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; }
/* returns: * <0 for error * -1 out of memory * -2 other * 0 for async dispatched * 1 for command completed (connection can be closed) */ int execute_or_dispatch_command(client_conn_t *conn, enum commands cmd, const char *argument) { int desc = conn->sd; char term = conn->term; const struct cl_engine *engine = conn->engine; /* execute commands that can be executed quickly on the recvloop thread, * these must: * - not involve any operation that can block for a long time, such as disk * I/O * - send of atomic message is allowed. * Dispatch other commands */ if (conn->group) { switch (cmd) { case COMMAND_FILDES: case COMMAND_SCAN: case COMMAND_END: case COMMAND_INSTREAM: case COMMAND_INSTREAMSCAN: case COMMAND_VERSION: case COMMAND_PING: case COMMAND_STATS: case COMMAND_COMMANDS: /* These commands are accepted inside IDSESSION */ break; default: /* these commands are not recognized inside an IDSESSION */ conn_reply_error(conn, "Command invalid inside IDSESSION."); logg("$SESSION: command is not valid inside IDSESSION: %d\n", cmd); conn->group = NULL; return 1; } } switch (cmd) { case COMMAND_SHUTDOWN: pthread_mutex_lock(&exit_mutex); progexit = 1; pthread_mutex_unlock(&exit_mutex); return 1; case COMMAND_RELOAD: pthread_mutex_lock(&reload_mutex); reload = 1; pthread_mutex_unlock(&reload_mutex); mdprintf(desc, "RELOADING%c", term); /* we set reload flag, and we'll reload before closing the * connection */ return 1; case COMMAND_PING: if (conn->group) mdprintf(desc, "%u: PONG%c", conn->id, term); else mdprintf(desc, "PONG%c", term); return conn->group ? 0 : 1; case COMMAND_VERSION: { if (conn->group) mdprintf(desc, "%u: ", conn->id); print_ver(desc, conn->term, engine); return conn->group ? 0 : 1; } case COMMAND_COMMANDS: { if (conn->group) mdprintf(desc, "%u: ", conn->id); print_commands(desc, conn->term, engine); return conn->group ? 0 : 1; } case COMMAND_DETSTATSCLEAR: { detstats_clear(); return 1; } case COMMAND_DETSTATS: { detstats_print(desc, conn->term); return 1; } case COMMAND_INSTREAM: { int rc = cli_gentempfd(optget(conn->opts, "TemporaryDirectory")->strarg, &conn->filename, &conn->scanfd); if (rc != CL_SUCCESS) return rc; conn->quota = optget(conn->opts, "StreamMaxLength")->numarg; conn->mode = MODE_STREAM; return 0; } case COMMAND_STREAM: case COMMAND_MULTISCAN: case COMMAND_CONTSCAN: case COMMAND_STATS: case COMMAND_FILDES: case COMMAND_SCAN: case COMMAND_INSTREAMSCAN: return dispatch_command(conn, cmd, argument); case COMMAND_IDSESSION: conn->group = thrmgr_group_new(); if (!conn->group) return CL_EMEM; return 0; case COMMAND_END: if (!conn->group) { /* end without idsession? */ conn_reply_single(conn, NULL, "UNKNOWN COMMAND"); return 1; } /* need to close connection if we were last in group */ return 1; /*case COMMAND_UNKNOWN:*/ default: conn_reply_single(conn, NULL, "UNKNOWN COMMAND"); return 1; } }
/* 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; int type = -1; /* TODO: make this enum */ 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 (STAT(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; default: logg("!Invalid command distpached: %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(STAT(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 scanfd(const client_conn_t *conn, unsigned long int *scanned, const struct cl_engine *engine, unsigned int options, const struct optstruct *opts, int odesc, int stream) { int ret, fd = conn->scanfd; const char *virname; STATBUF statbuf; struct cb_context context; char fdstr[32]; const char*reply_fdstr; UNUSEDPARAM(odesc); if (stream) { struct sockaddr_in sa; socklen_t salen = sizeof(sa); if(getpeername(conn->sd, (struct sockaddr *)&sa, &salen) || salen > sizeof(sa) || sa.sin_family != AF_INET) strncpy(fdstr, "instream(local)", sizeof(fdstr)); else snprintf(fdstr, sizeof(fdstr), "instream(%s@%u)", inet_ntoa(sa.sin_addr), ntohs(sa.sin_port)); reply_fdstr = "stream"; } else { snprintf(fdstr, sizeof(fdstr), "fd[%d]", fd); reply_fdstr = fdstr; } if(FSTAT(fd, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) { logg("%s: Not a regular file. ERROR\n", fdstr); if (conn_reply(conn, reply_fdstr, "Not a regular file", "ERROR") == -1) return CL_ETIMEOUT; return -1; } thrmgr_setactivetask(fdstr, NULL); context.filename = fdstr; context.virsize = 0; context.scandata = NULL; ret = cl_scandesc_callback(fd, &virname, scanned, engine, options, &context); thrmgr_setactivetask(NULL, NULL); if (thrmgr_group_need_terminate(conn->group)) { logg("*Client disconnected while scanjob was active\n"); return ret == CL_ETIMEOUT ? ret : CL_BREAK; } if(ret == CL_VIRUS) { if (conn_reply_virus(conn, reply_fdstr, virname) == -1) ret = CL_ETIMEOUT; if(context.virsize && optget(opts, "ExtendedDetectionInfo")->enabled) logg("%s: %s(%s:%llu) FOUND\n", fdstr, virname, context.virhash, context.virsize); else logg("%s: %s FOUND\n", fdstr, virname); virusaction(reply_fdstr, virname, opts); } else if(ret != CL_CLEAN) { if (conn_reply(conn, reply_fdstr, cl_strerror(ret), "ERROR") == -1) ret = CL_ETIMEOUT; logg("%s: %s ERROR\n", fdstr, cl_strerror(ret)); } else { if (conn_reply_single(conn, reply_fdstr, "OK") == CL_ETIMEOUT) ret = CL_ETIMEOUT; if(logok) logg("%s: OK\n", fdstr); } return ret; }
int scan_callback(STATBUF *sb, char *filename, const char *msg, enum cli_ftw_reason reason, struct cli_ftw_cbdata *data) { struct scan_cb_data *scandata = data->data; const char *virname = NULL; int ret; int type = scandata->type; struct cb_context context; /* detect disconnected socket, * this should NOT detect half-shutdown sockets (SHUT_WR) */ if (send(scandata->conn->sd, &ret, 0, 0) == -1 && errno != EINTR) { logg("$Client disconnected while command was active!\n"); thrmgr_group_terminate(scandata->conn->group); if (reason == visit_file) free(filename); return CL_BREAK; } if (thrmgr_group_need_terminate(scandata->conn->group)) { logg("^Client disconnected while scanjob was active\n"); if (reason == visit_file) free(filename); return CL_BREAK; } scandata->total++; switch (reason) { case error_mem: if (msg) logg("!Memory allocation failed during cli_ftw() on %s\n", msg); else logg("!Memory allocation failed during cli_ftw()\n"); scandata->errors++; return CL_EMEM; case error_stat: conn_reply_errno(scandata->conn, msg, "lstat() failed:"); logg("^lstat() failed on: %s\n", msg); scandata->errors++; return CL_SUCCESS; case warning_skipped_dir: logg("^Directory recursion limit reached, skipping %s\n", msg); return CL_SUCCESS; case warning_skipped_link: logg("$Skipping symlink: %s\n", msg); return CL_SUCCESS; case warning_skipped_special: if (msg == scandata->toplevel_path) conn_reply(scandata->conn, msg, "Not supported file type", "ERROR"); logg("*Not supported file type: %s\n", msg); return CL_SUCCESS; case visit_directory_toplev: return CL_SUCCESS; case visit_file: break; } /* check whether the file is excluded */ #ifdef C_LINUX if(procdev && sb && (sb->st_dev == procdev)) { free(filename); return CL_SUCCESS; } #endif if(sb && sb->st_size == 0) { /* empty file */ if (msg == scandata->toplevel_path) conn_reply_single(scandata->conn, filename, "Empty file"); free(filename); return CL_SUCCESS; } if (type == TYPE_MULTISCAN) { client_conn_t *client_conn = (client_conn_t *) calloc(1, sizeof(struct client_conn_tag)); if(client_conn) { client_conn->scanfd = -1; client_conn->sd = scandata->odesc; client_conn->filename = filename; client_conn->cmdtype = COMMAND_MULTISCANFILE; client_conn->term = scandata->conn->term; client_conn->options = scandata->options; client_conn->opts = scandata->opts; client_conn->group = scandata->group; if(cl_engine_addref(scandata->engine)) { logg("!cl_engine_addref() failed\n"); free(filename); free(client_conn); return CL_EMEM; } else { client_conn->engine = scandata->engine; pthread_mutex_lock(&reload_mutex); client_conn->engine_timestamp = reloaded_time; pthread_mutex_unlock(&reload_mutex); if(!thrmgr_group_dispatch(scandata->thr_pool, scandata->group, client_conn, 1)) { logg("!thread dispatch failed\n"); cl_engine_free(scandata->engine); free(filename); free(client_conn); return CL_EMEM; } } } else { logg("!Can't allocate memory for client_conn\n"); scandata->errors++; free(filename); return CL_EMEM; } return CL_SUCCESS; } if (access(filename, R_OK)) { if (conn_reply(scandata->conn, filename, "Access denied.", "ERROR") == -1) { free(filename); return CL_ETIMEOUT; } logg("*Access denied: %s\n", filename); scandata->errors++; free(filename); return CL_SUCCESS; } thrmgr_setactivetask(filename, NULL); context.filename = filename; context.virsize = 0; context.scandata = scandata; ret = cl_scanfile_callback(filename, &virname, &scandata->scanned, scandata->engine, scandata->options, &context); thrmgr_setactivetask(NULL, NULL); if (thrmgr_group_need_terminate(scandata->conn->group)) { free(filename); logg("*Client disconnected while scanjob was active\n"); return ret == CL_ETIMEOUT ? ret : CL_BREAK; } if ((ret == CL_VIRUS) && (virname == NULL)) { logg("*%s: reported CL_VIRUS but no virname returned!\n", filename); ret = CL_EMEM; } if (ret == CL_VIRUS) { scandata->infected++; if (scandata->options & CL_SCAN_ALLMATCHES) { if(optget(scandata->opts, "PreludeEnable")->enabled){ prelude_logging(filename, virname, context.virhash, context.virsize); } virusaction(filename, virname, scandata->opts); } else { if (conn_reply_virus(scandata->conn, filename, virname) == -1) { free(filename); return CL_ETIMEOUT; } if(optget(scandata->opts, "PreludeEnable")->enabled){ prelude_logging(filename, virname, context.virhash, context.virsize); } if(context.virsize && optget(scandata->opts, "ExtendedDetectionInfo")->enabled) logg("~%s: %s(%s:%llu) FOUND\n", filename, virname, context.virhash, context.virsize); else logg("~%s: %s FOUND\n", filename, virname); virusaction(filename, virname, scandata->opts); } } else if (ret != CL_CLEAN) { scandata->errors++; if (conn_reply(scandata->conn, filename, cl_strerror(ret), "ERROR") == -1) { free(filename); return CL_ETIMEOUT; } logg("~%s: %s ERROR\n", filename, cl_strerror(ret)); } else if (logok) { logg("~%s: OK\n", filename); } free(filename); if(ret == CL_EMEM) /* stop scanning */ return ret; if (type == TYPE_SCAN) { /* virus -> break */ return ret; } /* keep scanning always */ return CL_SUCCESS; }
/* 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; int type = -1; /* TODO: make this enum */ int maxdirrec; int ret = 0; int flags = CLI_FTW_STD; struct scan_cb_data scandata; struct cli_ftw_cbdata data; unsigned ok, error, total; 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: flags &= ~CLI_FTW_NEED_STAT; thrmgr_setactivetask(NULL, "MULTISCAN"); type = TYPE_MULTISCAN; scandata.group = group = thrmgr_group_new(); if (!group) return CL_EMEM; 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 CL_BREAK; } 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."); else { ret = scanfd(conn->scanfd, conn, NULL, engine, options, opts, desc, 0); if (ret == CL_VIRUS) { *virus = 1; } else if (ret == CL_EMEM) { if(optget(opts, "ExitOnOOM")->enabled) 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); 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; } return 0; case COMMAND_INSTREAMSCAN: thrmgr_setactivetask(NULL, "INSTREAM"); ret = scanfd(conn->scanfd, conn, NULL, engine, options, opts, desc, 1); if (ret == CL_VIRUS) { *virus = 1; } else if (ret == CL_EMEM) { if(optget(opts, "ExitOnOOM")->enabled) 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; } 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; ret = cli_ftw(conn->filename, flags, maxdirrec ? maxdirrec : INT_MAX, scan_callback, &data); if (ret == CL_EMEM) if(optget(opts, "ExitOnOOM")->enabled) return -1; if (scandata.group && conn->cmdtype == COMMAND_MULTISCAN) { thrmgr_group_waitforall(group, &ok, &error, &total); } 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; }