int scan_pathchk(const char *path, struct cli_ftw_cbdata *data) { struct scan_cb_data *scandata = data->data; const struct optstruct *opt; STATBUF statbuf; 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"); return 1; } opt = (const struct optstruct *) opt->nextarg; } } if(!optget(scandata->opts, "CrossFilesystems")->enabled) { if(CLAMSTAT(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; } } } return 0; }
static int isdir(void) { STATBUF sb; if(CLAMSTAT(actarget, &sb) || !S_ISDIR(sb.st_mode)) { logg("!'%s' doesn't exist or is not a directory\n", actarget); return 0; } return 1; }
static int logg_open(void) { STATBUF sb; if (logg_file) if (logg_size > 0) if (CLAMSTAT(logg_file, &sb) != -1) if (sb.st_size > logg_size) if (rename_logg(&sb)) return -1; return 0; }
int onas_fan_checkowner (int pid, const struct optstruct *opts) { char path[32]; STATBUF sb; const struct optstruct *opt; if (!(opt = optget (opts, "OnAccessExcludeUID"))->enabled) return 0; snprintf (path, sizeof (path), "/proc/%u", pid); if (CLAMSTAT (path, &sb) == 0) { while (opt) { if (opt->numarg == (long long) sb.st_uid) return 1; opt = opt->nextarg; } } return 0; }
int localserver(const struct optstruct *opts) { struct sockaddr_un server; int sockfd, backlog; STATBUF foo; char *estr; memset((char *) &server, 0, sizeof(server)); server.sun_family = AF_UNIX; strncpy(server.sun_path, optget(opts, "LocalSocket")->strarg, sizeof(server.sun_path)); server.sun_path[sizeof(server.sun_path)-1]='\0'; if((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { estr = strerror(errno); logg("!LOCAL: Socket allocation error: %s\n", estr); return -1; } if(bind(sockfd, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) == -1) { if(errno == EADDRINUSE) { if(connect(sockfd, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) >= 0) { logg("!LOCAL: Socket file %s is in use by another process.\n", server.sun_path); close(sockfd); return -1; } if(optget(opts, "FixStaleSocket")->enabled) { logg("#LOCAL: Removing stale socket file %s\n", server.sun_path); if(unlink(server.sun_path) == -1) { estr = strerror(errno); logg("!LOCAL: Socket file %s could not be removed: %s\n", server.sun_path, estr); close(sockfd); return -1; } if(bind(sockfd, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) == -1) { estr = strerror(errno); logg("!LOCAL: Socket file %s could not be bound: %s (unlink tried)\n", server.sun_path, estr); close(sockfd); return -1; } } else if(CLAMSTAT(server.sun_path, &foo) != -1) { logg("!LOCAL: Socket file %s exists. Either remove it, or configure a different one.\n", server.sun_path); close(sockfd); return -1; } } else { estr = strerror(errno); logg("!LOCAL: Socket file %s could not be bound: %s\n", server.sun_path, estr); close(sockfd); return -1; } } logg("#LOCAL: Unix socket file %s\n", server.sun_path); backlog = optget(opts, "MaxConnectionQueueLength")->numarg; logg("#LOCAL: Setting connection queue length to %d\n", backlog); if(listen(sockfd, backlog) == -1) { estr = strerror(errno); logg("!LOCAL: listen() error: %s\n", estr); close(sockfd); return -1; } return sockfd; }
/* Sends a proper scan request to clamd and parses its replies * This is used only in non IDSESSION mode * Returns the number of infected files or -1 on error * NOTE: filename may be NULL for STREAM scantype. */ int dsresult(int sockd, int scantype, const char *filename, int *printok, int *errors) { int infected = 0, len = 0, beenthere = 0; char *bol, *eol; struct RCVLN rcv; STATBUF sb; if(filename && chkpath(filename)) return 0; recvlninit(&rcv, sockd); switch(scantype) { case MULTI: case CONT: case ALLMATCH: if (!filename) { logg("Filename cannot be NULL for MULTISCAN or CONTSCAN.\n"); return -1; } len = strlen(filename) + strlen(scancmd[scantype]) + 3; if (!(bol = malloc(len))) { logg("!Cannot allocate a command buffer: %s\n", strerror(errno)); return -1; } sprintf(bol, "z%s %s", scancmd[scantype], filename); if(sendln(sockd, bol, len)) { free(bol); return -1; } free(bol); break; case STREAM: /* NULL filename safe in send_stream() */ len = send_stream(sockd, filename); break; #ifdef HAVE_FD_PASSING case FILDES: /* NULL filename safe in send_fdpass() */ len = send_fdpass(sockd, filename); break; #endif } if(len <=0) { *printok = 0; if(errors) (*errors)++; return len; } while((len = recvln(&rcv, &bol, &eol))) { if(len == -1) return -1; beenthere = 1; if(!filename) logg("~%s\n", bol); if(len > 7) { char *colon = strrchr(bol, ':'); if(colon && colon[1] != ' ') { char *br; *colon = 0; br = strrchr(bol, '('); if(br) *br = 0; colon = strrchr(bol, ':'); } if(!colon) { char * unkco = "UNKNOWN COMMAND"; if (!strncmp(bol, unkco, sizeof(unkco) - 1)) logg("clamd replied \"UNKNOWN COMMAND\". Command was %s\n", (scantype < 0 || scantype > MAX_SCANTYPE) ? "unidentified" : scancmd[scantype]); else logg("Failed to parse reply: \"%s\"\n", bol); return -1; } else if(!memcmp(eol - 7, " FOUND", 6)) { *(eol - 7) = 0; *printok = 0; infected++; if(filename) { if(scantype >= STREAM) { logg("~%s%s FOUND\n", filename, colon); if(action) action(filename); } else { logg("~%s FOUND\n", bol); *colon = '\0'; if(action) action(bol); } } } else if(!memcmp(eol-7, " ERROR", 6)) { if(errors) (*errors)++; *printok = 0; if(filename) { if(scantype >= STREAM) logg("~%s%s\n", filename, colon); else logg("~%s\n", bol); } } } } if(!beenthere) { if (!filename) { logg("STDIN: noreply from clamd\n."); return -1; } if(CLAMSTAT(filename, &sb) == -1) { logg("~%s: stat() failed with %s, clamd may not be responding\n", filename, strerror(errno)); return -1; } if(!S_ISDIR(sb.st_mode)) { logg("~%s: no reply from clamd\n", filename); return -1; } } return infected; }
int main(int argc, char **argv) { static struct cl_engine *engine = NULL; const struct optstruct *opt; #ifndef _WIN32 struct passwd *user = NULL; struct sigaction sa; struct rlimit rlim; #endif time_t currtime; const char *dbdir, *cfgfile; char *pua_cats = NULL, *pt; int ret, tcpsock = 0, localsock = 0, i, min_port, max_port; unsigned int sigs = 0; int lsockets[2], nlsockets = 0; unsigned int dboptions = 0; #ifdef C_LINUX STATBUF sb; #endif if(check_flevel()) exit(1); #ifndef _WIN32 memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_IGN; sigaction(SIGHUP, &sa, NULL); sigaction(SIGUSR2, &sa, NULL); #endif if((opts = optparse(NULL, argc, argv, 1, OPT_CLAMD, 0, NULL)) == NULL) { mprintf("!Can't parse command line options\n"); return 1; } if(optget(opts, "help")->enabled) { help(); optfree(opts); return 0; } if(optget(opts, "debug")->enabled) { #if defined(C_LINUX) /* [email protected]: create a dump if needed */ rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY; if(setrlimit(RLIMIT_CORE, &rlim) < 0) perror("setrlimit"); #endif debug_mode = 1; } /* parse the config file */ cfgfile = optget(opts, "config-file")->strarg; pt = strdup(cfgfile); if((opts = optparse(cfgfile, 0, NULL, 1, OPT_CLAMD, 0, opts)) == NULL) { fprintf(stderr, "ERROR: Can't open/parse the config file %s\n", pt); free(pt); return 1; } free(pt); if(optget(opts, "version")->enabled) { print_version(optget(opts, "DatabaseDirectory")->strarg); optfree(opts); return 0; } /* drop privileges */ #ifndef _WIN32 if(geteuid() == 0 && (opt = optget(opts, "User"))->enabled) { if((user = getpwnam(opt->strarg)) == NULL) { fprintf(stderr, "ERROR: Can't get information about user %s.\n", opt->strarg); optfree(opts); return 1; } if(optget(opts, "AllowSupplementaryGroups")->enabled) { #ifdef HAVE_INITGROUPS if(initgroups(opt->strarg, user->pw_gid)) { fprintf(stderr, "ERROR: initgroups() failed.\n"); optfree(opts); return 1; } #else mprintf("!AllowSupplementaryGroups: initgroups() is not available, please disable AllowSupplementaryGroups in %s\n", cfgfile); optfree(opts); return 1; #endif } else { #ifdef HAVE_SETGROUPS if(setgroups(1, &user->pw_gid)) { fprintf(stderr, "ERROR: setgroups() failed.\n"); optfree(opts); return 1; } #endif } if(setgid(user->pw_gid)) { fprintf(stderr, "ERROR: setgid(%d) failed.\n", (int) user->pw_gid); optfree(opts); return 1; } if(setuid(user->pw_uid)) { fprintf(stderr, "ERROR: setuid(%d) failed.\n", (int) user->pw_uid); optfree(opts); return 1; } } #endif /* initialize logger */ logg_lock = !optget(opts, "LogFileUnlock")->enabled; logg_time = optget(opts, "LogTime")->enabled; logok = optget(opts, "LogClean")->enabled; logg_size = optget(opts, "LogFileMaxSize")->numarg; logg_verbose = mprintf_verbose = optget(opts, "LogVerbose")->enabled; if (logg_size) logg_rotate = optget(opts, "LogRotate")->enabled; mprintf_send_timeout = optget(opts, "SendBufTimeout")->numarg; do { /* logger initialized */ if((opt = optget(opts, "LogFile"))->enabled) { char timestr[32]; logg_file = opt->strarg; if(!cli_is_abspath(logg_file)) { fprintf(stderr, "ERROR: LogFile requires full path.\n"); ret = 1; break; } time(&currtime); if(logg("#+++ Started at %s", cli_ctime(&currtime, timestr, sizeof(timestr)))) { fprintf(stderr, "ERROR: Can't initialize the internal logger\n"); ret = 1; break; } } else logg_file = NULL; if (optget(opts,"DevLiblog")->enabled) cl_set_clcb_msg(msg_callback); if((ret = cl_init(CL_INIT_DEFAULT))) { logg("!Can't initialize libclamav: %s\n", cl_strerror(ret)); ret = 1; break; } if(optget(opts, "Debug")->enabled) /* enable debug messages in libclamav */ { cl_debug(); logg_verbose = 2; } #if defined(USE_SYSLOG) && !defined(C_AIX) if(optget(opts, "LogSyslog")->enabled) { int fac = LOG_LOCAL6; opt = optget(opts, "LogFacility"); if((fac = logg_facility(opt->strarg)) == -1) { logg("!LogFacility: %s: No such facility.\n", opt->strarg); ret = 1; break; } openlog("clamd", LOG_PID, fac); logg_syslog = 1; } #endif #ifdef C_LINUX procdev = 0; if(CLAMSTAT("/proc", &sb) != -1 && !sb.st_size) procdev = sb.st_dev; #endif /* check socket type */ if(optget(opts, "TCPSocket")->enabled) tcpsock = 1; if(optget(opts, "LocalSocket")->enabled) localsock = 1; if(!tcpsock && !localsock) { logg("!Please define server type (local and/or TCP).\n"); ret = 1; break; } logg("#clamd daemon %s (OS: "TARGET_OS_TYPE", ARCH: "TARGET_ARCH_TYPE", CPU: "TARGET_CPU_TYPE")\n", get_version()); #ifndef _WIN32 if(user) logg("#Running as user %s (UID %u, GID %u)\n", user->pw_name, user->pw_uid, user->pw_gid); #endif #if defined(RLIMIT_DATA) && defined(C_BSD) if (getrlimit(RLIMIT_DATA, &rlim) == 0) { /* bb #1941. * On 32-bit FreeBSD if you set ulimit -d to >2GB then mmap() will fail * too soon (after ~120 MB). * Set limit lower than 2G if on 32-bit */ uint64_t lim = rlim.rlim_cur; if (sizeof(void*) == 4 && lim > (1ULL << 31)) { rlim.rlim_cur = 1ULL << 31; if (setrlimit(RLIMIT_DATA, &rlim) < 0) logg("!setrlimit(RLIMIT_DATA) failed: %s\n", strerror(errno)); else logg("Running on 32-bit system, and RLIMIT_DATA > 2GB, lowering to 2GB!\n"); } } #endif if(logg_size) logg("#Log file size limited to %u bytes.\n", logg_size); else logg("#Log file size limit disabled.\n"); min_port = optget(opts, "StreamMinPort")->numarg; max_port = optget(opts, "StreamMaxPort")->numarg; if (min_port < 1024 || min_port > max_port || max_port > 65535) { logg("!Invalid StreamMinPort/StreamMaxPort: %d, %d\n", min_port, max_port); ret = 1; break; } if(!(engine = cl_engine_new())) { logg("!Can't initialize antivirus engine\n"); ret = 1; break; } /* load the database(s) */ dbdir = optget(opts, "DatabaseDirectory")->strarg; logg("#Reading databases from %s\n", dbdir); if(optget(opts, "DetectPUA")->enabled) { dboptions |= CL_DB_PUA; if((opt = optget(opts, "ExcludePUA"))->enabled) { dboptions |= CL_DB_PUA_EXCLUDE; i = 0; logg("#Excluded PUA categories:"); 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); ret = 1; break; } logg("# %s", opt->strarg); sprintf(pua_cats + i, ".%s", opt->strarg); i += strlen(opt->strarg) + 1; pua_cats[i] = 0; opt = opt->nextarg; } if (ret) break; logg("#\n"); pua_cats[i] = '.'; pua_cats[i + 1] = 0; } if((opt = optget(opts, "IncludePUA"))->enabled) { if(pua_cats) { logg("!ExcludePUA and IncludePUA cannot be used at the same time\n"); free(pua_cats); ret = 1; break; } dboptions |= CL_DB_PUA_INCLUDE; i = 0; logg("#Included PUA categories:"); while(opt) { if(!(pua_cats = realloc(pua_cats, i + strlen(opt->strarg) + 3))) { logg("!Can't allocate memory for pua_cats\n"); ret = 1; break; } logg("# %s", opt->strarg); sprintf(pua_cats + i, ".%s", opt->strarg); i += strlen(opt->strarg) + 1; pua_cats[i] = 0; opt = opt->nextarg; } if (ret) break; logg("#\n"); 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); ret = 1; break; } free(pua_cats); } } else { logg("#Not loading PUA signatures.\n"); } if(optget(opts, "OfficialDatabaseOnly")->enabled) { dboptions |= CL_DB_OFFICIAL_ONLY; logg("#Only loading official signatures.\n"); } /* set the temporary dir */ if((opt = optget(opts, "TemporaryDirectory"))->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)); ret = 1; break; } } cl_engine_set_clcb_hash(engine, hash_callback); detstats_clear(); if(optget(opts, "LeaveTemporaryFiles")->enabled) cl_engine_set_num(engine, CL_ENGINE_KEEPTMP, 1); if(optget(opts, "PhishingSignatures")->enabled) dboptions |= CL_DB_PHISHING; else logg("#Not loading phishing signatures.\n"); if(optget(opts,"Bytecode")->enabled) { dboptions |= CL_DB_BYTECODE; if((opt = optget(opts,"BytecodeSecurity"))->enabled) { enum bytecode_security s; if (!strcmp(opt->strarg, "TrustSigned")) { s = CL_BYTECODE_TRUST_SIGNED; logg("#Bytecode: Security mode set to \"TrustSigned\".\n"); } else if (!strcmp(opt->strarg, "Paranoid")) { s = CL_BYTECODE_TRUST_NOTHING; logg("#Bytecode: Security mode set to \"Paranoid\".\n"); } else { logg("!Unable to parse bytecode security setting:%s\n", opt->strarg); ret = 1; break; } if ((ret = cl_engine_set_num(engine, CL_ENGINE_BYTECODE_SECURITY, s))) { logg("^Invalid bytecode security setting %s: %s\n", opt->strarg, cl_strerror(ret)); ret = 1; break; } } if((opt = optget(opts,"BytecodeUnsigned"))->enabled) { dboptions |= CL_DB_BYTECODE_UNSIGNED; logg("#Bytecode: Enabled support for unsigned bytecode.\n"); } if((opt = optget(opts,"BytecodeMode"))->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,"BytecodeTimeout"))->enabled) { cl_engine_set_num(engine, CL_ENGINE_BYTECODE_TIMEOUT, opt->numarg); } } else logg("#Bytecode support disabled.\n"); if(optget(opts,"PhishingScanURLs")->enabled) dboptions |= CL_DB_PHISHING_URLS; else logg("#Disabling URL based phishing detection.\n"); if(optget(opts,"DevACOnly")->enabled) { logg("#Only using the A-C matcher.\n"); cl_engine_set_num(engine, CL_ENGINE_AC_ONLY, 1); } if((opt = optget(opts, "DevACDepth"))->enabled) { cl_engine_set_num(engine, CL_ENGINE_AC_MAXDEPTH, opt->numarg); logg("#Max A-C depth set to %u\n", (unsigned int) opt->numarg); } if((ret = cl_load(dbdir, engine, &sigs, dboptions))) { logg("!%s\n", cl_strerror(ret)); ret = 1; break; } if (optget(opts, "DisableCertCheck")->enabled) engine->dconf->pe |= PE_CONF_DISABLECERT; logg("#Loaded %u signatures.\n", sigs); if((ret = cl_engine_compile(engine)) != 0) { logg("!Database initialization error: %s\n", cl_strerror(ret)); ret = 1; break; } if(tcpsock) { if ((lsockets[nlsockets] = tcpserver(opts)) == -1) { ret = 1; break; } nlsockets++; } #ifndef _WIN32 if(localsock) { mode_t sock_mode, umsk = umask(0777); /* socket is created with 000 to avoid races */ if ((lsockets[nlsockets] = localserver(opts)) == -1) { ret = 1; umask(umsk); break; } umask(umsk); /* restore umask */ if(optget(opts, "LocalSocketGroup")->enabled) { char *gname = optget(opts, "LocalSocketGroup")->strarg, *end; gid_t sock_gid = strtol(gname, &end, 10); if(*end) { struct group *pgrp = getgrnam(gname); if(!pgrp) { logg("!Unknown group %s\n", gname); ret = 1; break; } sock_gid = pgrp->gr_gid; } if(chown(optget(opts, "LocalSocket")->strarg, -1, sock_gid)) { logg("!Failed to change socket ownership to group %s\n", gname); ret = 1; break; } } if(optget(opts, "LocalSocketMode")->enabled) { char *end; sock_mode = strtol(optget(opts, "LocalSocketMode")->strarg, &end, 8); if(*end) { logg("!Invalid LocalSocketMode %s\n", optget(opts, "LocalSocketMode")->strarg); ret = 1; break; } } else sock_mode = 0777 /* & ~umsk*/; /* conservative default: umask was 0 in clamd < 0.96 */ if(chmod(optget(opts, "LocalSocket")->strarg, sock_mode & 0666)) { logg("!Cannot set socket permission to %s\n", optget(opts, "LocalSocketMode")->strarg); ret = 1; break; } nlsockets++; } /* fork into background */ if(!optget(opts, "Foreground")->enabled) { #ifdef C_BSD /* workaround for OpenBSD bug, see https://wwws.clamav.net/bugzilla/show_bug.cgi?id=885 */ for(ret=0;ret<nlsockets;ret++) { if (fcntl(lsockets[ret], F_SETFL, fcntl(lsockets[ret], F_GETFL) | O_NONBLOCK) == -1) { logg("!fcntl for lsockets[] failed\n"); close(lsockets[ret]); ret = 1; break; } } #endif gengine = engine; atexit(free_engine); if(daemonize() == -1) { logg("!daemonize() failed: %s\n", strerror(errno)); ret = 1; break; } gengine = NULL; #ifdef C_BSD for(ret=0;ret<nlsockets;ret++) { if (fcntl(lsockets[ret], F_SETFL, fcntl(lsockets[ret], F_GETFL) & ~O_NONBLOCK) == -1) { logg("!fcntl for lsockets[] failed\n"); close(lsockets[ret]); ret = 1; break; } } #endif if(!debug_mode) if(chdir("/") == -1) logg("^Can't change current working directory to root\n"); } else foreground = 1; #endif ret = recvloop_th(lsockets, nlsockets, engine, dboptions, opts); } while (0); logg("*Closing the main socket%s.\n", (nlsockets > 1) ? "s" : ""); for (i = 0; i < nlsockets; i++) { closesocket(lsockets[i]); } #ifndef _WIN32 if(nlsockets && localsock) { opt = optget(opts, "LocalSocket"); if(unlink(opt->strarg) == -1) logg("!Can't unlink the socket file %s\n", opt->strarg); else logg("Socket file removed.\n"); } #endif logg_close(); optfree(opts); return ret; }
/* returns * -1 on fatal error (shutdown) * 0 on ok * >0 errors encountered */ int command(client_conn_t *conn, int *virus) { int desc = conn->sd; struct cl_engine *engine = conn->engine; unsigned int options = conn->options; const struct optstruct *opts = conn->opts; enum scan_type type = TYPE_INIT; int maxdirrec; int ret = 0; int flags = CLI_FTW_STD; struct scan_cb_data scandata; struct cli_ftw_cbdata data; unsigned ok, error, total; STATBUF sb; jobgroup_t *group = NULL; if (thrmgr_group_need_terminate(conn->group)) { logg("$Client disconnected while command was active\n"); if (conn->scanfd != -1) close(conn->scanfd); return 1; } thrmgr_setactiveengine(engine); data.data = &scandata; memset(&scandata, 0, sizeof(scandata)); scandata.id = conn->id; scandata.group = conn->group; scandata.odesc = desc; scandata.conn = conn; scandata.options = options; scandata.engine = engine; scandata.opts = opts; scandata.thr_pool = conn->thrpool; scandata.toplevel_path = conn->filename; switch (conn->cmdtype) { case COMMAND_SCAN: thrmgr_setactivetask(NULL, "SCAN"); type = TYPE_SCAN; break; case COMMAND_CONTSCAN: thrmgr_setactivetask(NULL, "CONTSCAN"); type = TYPE_CONTSCAN; break; case COMMAND_MULTISCAN: { int multiscan, max, alive; /* use MULTISCAN only for directories (bb #1869) */ if (CLAMSTAT(conn->filename, &sb) == 0 && !S_ISDIR(sb.st_mode)) { thrmgr_setactivetask(NULL, "CONTSCAN"); type = TYPE_CONTSCAN; break; } pthread_mutex_lock(&conn->thrpool->pool_mutex); multiscan = conn->thrpool->thr_multiscan; max = conn->thrpool->thr_max; if (multiscan+1 < max) conn->thrpool->thr_multiscan = multiscan+1; else { alive = conn->thrpool->thr_alive; ret = -1; } pthread_mutex_unlock(&conn->thrpool->pool_mutex); if (ret) { /* multiscan has 1 control thread, so there needs to be at least 1 threads that is a non-multiscan controlthread to scan and make progress. */ logg("^Not enough threads for multiscan. Max: %d, Alive: %d, Multiscan: %d+1\n", max, alive, multiscan); conn_reply(conn, conn->filename, "Not enough threads for multiscan. Increase MaxThreads.", "ERROR"); return 1; } flags &= ~CLI_FTW_NEED_STAT; thrmgr_setactivetask(NULL, "MULTISCAN"); type = TYPE_MULTISCAN; scandata.group = group = thrmgr_group_new(); if (!group) { if(optget(opts, "ExitOnOOM")->enabled) return -1; else return 1; } break; } case COMMAND_MULTISCANFILE: thrmgr_setactivetask(NULL, "MULTISCANFILE"); scandata.group = NULL; scandata.type = TYPE_SCAN; scandata.thr_pool = NULL; /* TODO: check ret value */ ret = scan_callback(NULL, conn->filename, conn->filename, visit_file, &data); /* callback freed it */ conn->filename = NULL; *virus = scandata.infected; if (ret == CL_BREAK) { thrmgr_group_terminate(conn->group); return 1; } return scandata.errors > 0 ? scandata.errors : 0; case COMMAND_FILDES: thrmgr_setactivetask(NULL, "FILDES"); #ifdef HAVE_FD_PASSING if (conn->scanfd == -1) { conn_reply_error(conn, "FILDES: didn't receive file descriptor."); return 1; } else { ret = scanfd(conn, NULL, engine, options, opts, desc, 0); if (ret == CL_VIRUS) { *virus = 1; ret = 0; } else if (ret == CL_EMEM) { if(optget(opts, "ExitOnOOM")->enabled) ret = -1; else ret = 1; } else if (ret == CL_ETIMEOUT) { thrmgr_group_terminate(conn->group); ret = 1; } else ret = 0; logg("$Closed fd %d\n", conn->scanfd); close(conn->scanfd); } return ret; #else conn_reply_error(conn, "FILDES support not compiled in."); close(conn->scanfd); return 0; #endif case COMMAND_STATS: thrmgr_setactivetask(NULL, "STATS"); if (conn->group) mdprintf(desc, "%u: ", conn->id); thrmgr_printstats(desc, conn->term); return 0; case COMMAND_STREAM: thrmgr_setactivetask(NULL, "STREAM"); ret = scanstream(desc, NULL, engine, options, opts, conn->term); if (ret == CL_VIRUS) *virus = 1; if (ret == CL_EMEM) { if(optget(opts, "ExitOnOOM")->enabled) return -1; else return 1; } return 0; case COMMAND_INSTREAMSCAN: thrmgr_setactivetask(NULL, "INSTREAM"); ret = scanfd(conn, NULL, engine, options, opts, desc, 1); if (ret == CL_VIRUS) { *virus = 1; ret = 0; } else if (ret == CL_EMEM) { if(optget(opts, "ExitOnOOM")->enabled) ret = -1; else ret = 1; } else if (ret == CL_ETIMEOUT) { thrmgr_group_terminate(conn->group); ret = 1; } else ret = 0; if (ftruncate(conn->scanfd, 0) == -1) { /* not serious, we're going to close it and unlink it anyway */ logg("*ftruncate failed: %d\n", errno); } close(conn->scanfd); conn->scanfd = -1; cli_unlink(conn->filename); return ret; case COMMAND_ALLMATCHSCAN: if (!optget(opts, "AllowAllMatchScan")->enabled) { logg("$Rejecting ALLMATCHSCAN command.\n"); conn_reply(conn, conn->filename, "ALLMATCHSCAN command disabled by clamd configuration.", "ERROR"); return 1; } thrmgr_setactivetask(NULL, "ALLMATCHSCAN"); scandata.options |= CL_SCAN_ALLMATCHES; type = TYPE_SCAN; break; default: logg("!Invalid command dispatched: %d\n", conn->cmdtype); return 1; } scandata.type = type; maxdirrec = optget(opts, "MaxDirectoryRecursion")->numarg; if (optget(opts, "FollowDirectorySymlinks")->enabled) flags |= CLI_FTW_FOLLOW_DIR_SYMLINK; if (optget(opts, "FollowFileSymlinks")->enabled) flags |= CLI_FTW_FOLLOW_FILE_SYMLINK; if(!optget(opts, "CrossFilesystems")->enabled) if(CLAMSTAT(conn->filename, &sb) == 0) scandata.dev = sb.st_dev; ret = cli_ftw(conn->filename, flags, maxdirrec ? maxdirrec : INT_MAX, scan_callback, &data, scan_pathchk); if (ret == CL_EMEM) { if(optget(opts, "ExitOnOOM")->enabled) return -1; else return 1; } if (scandata.group && type == TYPE_MULTISCAN) { thrmgr_group_waitforall(group, &ok, &error, &total); pthread_mutex_lock(&conn->thrpool->pool_mutex); conn->thrpool->thr_multiscan--; pthread_mutex_unlock(&conn->thrpool->pool_mutex); } else { error = scandata.errors; total = scandata.total; ok = total - error - scandata.infected; } if (ok + error == total && (error != total)) { if (conn_reply_single(conn, conn->filename, "OK") == -1) ret = CL_ETIMEOUT; } *virus = total - (ok + error); if (ret == CL_ETIMEOUT) thrmgr_group_terminate(conn->group); return error; }
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, "yara-rules")->enabled) { char *p = optget(opts, "yara-rules")->strarg; if(strcmp(p, "yes")) { if(!strcmp(p, "only")) dboptions |= CL_DB_YARA_ONLY; else if (!strcmp(p, "no")) dboptions |= CL_DB_YARA_EXCLUDE; } } 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; } cl_engine_set_clcb_virus_found(engine, clamscan_virus_found_cb); if (optget(opts, "disable-cache")->enabled) cl_engine_set_num(engine, CL_ENGINE_DISABLE_CACHE, 1); if (optget(opts, "disable-pe-stats")->enabled) { cl_engine_set_num(engine, CL_ENGINE_DISABLE_PE_STATS, 1); } if (optget(opts, "enable-stats")->enabled) { cl_engine_stats_enable(engine); } if (optget(opts, "stats-timeout")->enabled) { cl_engine_set_num(engine, CL_ENGINE_STATS_TIMEOUT, optget(opts, "StatsTimeout")->numarg); } if (optget(opts, "stats-host-id")->enabled) { char *p = optget(opts, "stats-host-id")->strarg; if (strcmp(p, "default")) { if (!strcmp(p, "none")) { cl_engine_set_clcb_stats_get_hostid(engine, NULL); } else if (!strcmp(p, "anonymous")) { strcpy(hostid, STATS_ANON_UUID); } else { if (strlen(p) > 36) { logg("!Invalid HostID\n"); cl_engine_set_clcb_stats_submit(engine, NULL); cl_engine_free(engine); return 2; } strcpy(hostid, p); } cl_engine_set_clcb_stats_get_hostid(engine, get_hostid); } } 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, "force-to-disk")->enabled) cl_engine_set_num(engine, CL_ENGINE_FORCETODISK, 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 (optget(opts, "nocerts")->enabled) cl_engine_set_num(engine, CL_ENGINE_DISABLE_PE_CERTS, 1); if (optget(opts, "dumpcerts")->enabled) cl_engine_set_num(engine, CL_ENGINE_PE_DUMPCERTS, 1); 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, "statistics"))->enabled) { while(opt) { if (!strcasecmp(opt->strarg, "bytecode")) { dboptions |= CL_DB_BYTECODE_STATS; } else if (!strcasecmp(opt->strarg, "pcre")) { dboptions |= CL_DB_PCRE_STATS; } opt = opt->nextarg; } } 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); } /* pcre engine limits - required for cl_engine_compile */ if ((opt = optget(opts, "pcre-match-limit"))->active) { if ((ret = cl_engine_set_num(engine, CL_ENGINE_PCRE_MATCH_LIMIT, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_PCRE_MATCH_LIMIT) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } if ((opt = optget(opts, "pcre-recmatch-limit"))->active) { if ((ret = cl_engine_set_num(engine, CL_ENGINE_PCRE_RECMATCH_LIMIT, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_PCRE_RECMATCH_LIMIT) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } 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; } } /* Engine max sizes */ if((opt = optget(opts, "max-embeddedpe"))->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 2; } } if((opt = optget(opts, "max-htmlnormalize"))->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 2; } } if((opt = optget(opts, "max-htmlnotags"))->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 2; } } if((opt = optget(opts, "max-scriptnormalize"))->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 2; } } if((opt = optget(opts, "max-ziptypercg"))->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 2; } } if((opt = optget(opts, "max-partitions"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_PARTITIONS, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MAX_PARTITIONS) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } if((opt = optget(opts, "max-iconspe"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_ICONSPE, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MAX_ICONSPE) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } if((opt = optget(opts, "max-rechwp3"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_RECHWP3, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MAX_RECHWP3) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } if ((opt = optget(opts, "timelimit"))->active) { if ((ret = cl_engine_set_num(engine, CL_ENGINE_TIME_LIMIT, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_TIME_LIMIT) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } if ((opt = optget(opts, "pcre-max-filesize"))->active) { if ((ret = cl_engine_set_num(engine, CL_ENGINE_PCRE_MAX_FILESIZE, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_PCRE_MAX_FILESIZE) failed: %s\n", cl_strerror(ret)); cl_engine_free(engine); return 2; } } /* set scan options */ if(optget(opts, "allmatch")->enabled) { options |= CL_SCAN_ALLMATCHES; } 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,"partition-intersection")->enabled) options |= CL_SCAN_PARTITION_INTXN; 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, "block-macros")->enabled) options |= CL_SCAN_BLOCKMACROS; 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-swf")->enabled) options |= CL_SCAN_SWF; if(optget(opts, "scan-html")->enabled && optget(opts, "normalize")->enabled) options |= CL_SCAN_HTML; if(optget(opts, "scan-mail")->enabled) options |= CL_SCAN_MAIL; if(optget(opts, "scan-xmldocs")->enabled) options |= CL_SCAN_XMLDOCS; if(optget(opts, "scan-hwp3")->enabled) options |= CL_SCAN_HWP3; if(optget(opts, "algorithmic-detection")->enabled) options |= CL_SCAN_ALGORITHMIC; if(optget(opts, "block-max")->enabled) { options |= CL_SCAN_BLOCKMAX; } #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(CLAMSTAT("/proc", &sb) != -1 && !sb.st_size) procdev = sb.st_dev; #endif #if HAVE_JSON if (optget(opts, "gen-json")->enabled) options |= CL_SCAN_FILE_PROPERTIES; #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 { CLAMSTAT(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) { perror(file); logg("^%s: Can't access file\n", 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(CLAMSTAT(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); } } if((opt = optget(opts, "statistics"))->enabled) { while(opt) { if (!strcasecmp(opt->strarg, "bytecode")) { cli_sigperf_print(); cli_sigperf_events_destroy(); } #if HAVE_PCRE else if (!strcasecmp(opt->strarg, "pcre")) { cli_pcre_perf_print(); cli_pcre_perf_events_destroy(); } #endif opt = opt->nextarg; } } /* 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; }
static void scandirs(const char *dirname, struct cl_engine *engine, const struct optstruct *opts, unsigned int options, unsigned int depth, dev_t dev) { DIR *dd; struct dirent *dent; STATBUF sb; char *fname; int included; const struct optstruct *opt; unsigned int dirlnk, filelnk; if((opt = optget(opts, "exclude-dir"))->enabled) { while(opt) { if(match_regex(dirname, opt->strarg) == 1) { if(!printinfected) logg("~%s: Excluded\n", dirname); return; } opt = opt->nextarg; } } if((opt = optget(opts, "include-dir"))->enabled) { included = 0; while(opt) { if(match_regex(dirname, opt->strarg) == 1) { included = 1; break; } opt = opt->nextarg; } if(!included) { if(!printinfected) logg("~%s: Excluded\n", dirname); return; } } if(depth > (unsigned int) optget(opts, "max-dir-recursion")->numarg) return; dirlnk = optget(opts, "follow-dir-symlinks")->numarg; filelnk = optget(opts, "follow-file-symlinks")->numarg; if((dd = opendir(dirname)) != NULL) { info.dirs++; depth++; while((dent = readdir(dd))) { if(dent->d_ino) { if(strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..")) { /* build the full name */ fname = malloc(strlen(dirname) + strlen(dent->d_name) + 2); if (fname == NULL) { /* oops, malloc() failed, print warning and return */ logg("!scandirs: Memory allocation failed for fname\n"); break; } if(!strcmp(dirname, PATHSEP)) sprintf(fname, PATHSEP"%s", dent->d_name); else sprintf(fname, "%s"PATHSEP"%s", dirname, dent->d_name); /* stat the file */ if(LSTAT(fname, &sb) != -1) { if(!optget(opts, "cross-fs")->enabled) { if(sb.st_dev != dev) { if(!printinfected) logg("~%s: Excluded\n", fname); free(fname); continue; } } if(S_ISLNK(sb.st_mode)) { if(dirlnk != 2 && filelnk != 2) { if(!printinfected) logg("%s: Symbolic link\n", fname); } else if(CLAMSTAT(fname, &sb) != -1) { if(S_ISREG(sb.st_mode) && filelnk == 2) { scanfile(fname, engine, opts, options); } else if(S_ISDIR(sb.st_mode) && dirlnk == 2) { if(recursion) scandirs(fname, engine, opts, options, depth, dev); } else { if(!printinfected) logg("%s: Symbolic link\n", fname); } } } else if(S_ISREG(sb.st_mode)) { scanfile(fname, engine, opts, options); } else if(S_ISDIR(sb.st_mode) && recursion) { scandirs(fname, engine, opts, options, depth, dev); } } free(fname); } } } closedir(dd); } else { if(!printinfected) logg("~%s: Can't open directory.\n", dirname); info.errors++; } }
static void scanfile(const char *filename, struct cl_engine *engine, const struct optstruct *opts, unsigned int options) { int ret = 0, fd, included; unsigned i; const struct optstruct *opt; const char *virname; STATBUF sb; struct metachain chain; struct clamscan_cb_data data; if((opt = optget(opts, "exclude"))->enabled) { while(opt) { if(match_regex(filename, opt->strarg) == 1) { if(!printinfected) logg("~%s: Excluded\n", filename); return; } opt = opt->nextarg; } } if((opt = optget(opts, "include"))->enabled) { included = 0; while(opt) { if(match_regex(filename, opt->strarg) == 1) { included = 1; break; } opt = opt->nextarg; } if(!included) { if(!printinfected) logg("~%s: Excluded\n", filename); return; } } /* argh, don't scan /proc files */ if(CLAMSTAT(filename, &sb) != -1) { #ifdef C_LINUX if(procdev && sb.st_dev == procdev) { if(!printinfected) logg("~%s: Excluded (/proc)\n", filename); return; } #endif if(!sb.st_size) { if(!printinfected) logg("~%s: Empty file\n", filename); return; } info.rblocks += sb.st_size / CL_COUNT_PRECISION; } #ifndef _WIN32 if(geteuid()) { if(checkaccess(filename, NULL, R_OK) != 1) { if(!printinfected) logg("~%s: Access denied\n", filename); info.errors++; return; } } #endif memset(&chain, 0, sizeof(chain)); if(optget(opts, "archive-verbose")->enabled) { chain.chains = malloc(sizeof(char **)); if (chain.chains) { chain.chains[0] = strdup(filename); if (!chain.chains[0]) { free(chain.chains); logg("Unable to allocate memory in scanfile()\n"); info.errors++; return; } chain.nchains = 1; } } logg("*Scanning %s\n", filename); if((fd = safe_open(filename, O_RDONLY|O_BINARY)) == -1) { logg("^Can't open file %s: %s\n", filename, strerror(errno)); info.errors++; return; } data.chain = &chain; data.filename = filename; if((ret = cl_scandesc_callback(fd, &virname, &info.blocks, engine, options, &data)) == CL_VIRUS) { if(optget(opts, "archive-verbose")->enabled) { if (chain.nchains > 1) { char str[128]; int toolong = print_chain(&chain, str, sizeof(str)); logg("~%s%s!(%llu)%s: %s FOUND\n", str, toolong ? "..." : "", (long long unsigned)(chain.lastvir-1), chain.chains[chain.nchains-1], virname); } else if (chain.lastvir) { logg("~%s!(%llu): %s FOUND\n", filename, (long long unsigned)(chain.lastvir-1), virname); } } info.files++; info.ifiles++; if(bell) fprintf(stderr, "\007"); } else if(ret == CL_CLEAN) { if(!printinfected && printclean) mprintf("~%s: OK\n", filename); info.files++; } else { if(!printinfected) logg("~%s: %s ERROR\n", filename, cl_strerror(ret)); info.errors++; } for (i=0;i<chain.nchains;i++) free(chain.chains[i]); free(chain.chains); close(fd); if(ret == CL_VIRUS && action) action(filename); }