/* * Copyright (C) 2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved. * * Authors: Mickey Sola * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #if HAVE_CONFIG_H #include "clamav-config.h" #endif #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifndef _WIN32 #include #endif #include #include #if defined(FANOTIFY) #include #include #endif #include "libclamav/clamav.h" #include "libclamav/others.h" #include "shared/output.h" #include "shared/misc.h" #include "shared/optparser.h" #include "shared/actions.h" #include "clamonacc.h" #include "client/client.h" #include "fanotif/fanotif.h" #include "inotif/inotif.h" #include "scan/queue.h" pthread_t ddd_pid = 0; pthread_t scan_queue_pid = 0; static void onas_handle_signals(); static int startup_checks(struct onas_context *ctx); static struct onas_context *g_ctx = NULL; static void onas_clamonacc_exit(int sig) { logg("*Clamonacc: onas_clamonacc_exit(), signal %d\n", sig); if (sig == 11) { logg("!Clamonacc: clamonacc has experienced a fatal error, if you continue to see this error, please run clamonacc with --debug and report the issue and crash report to the developpers\n"); } if (g_ctx) { if (g_ctx->fan_fd) { close(g_ctx->fan_fd); } g_ctx->fan_fd = 0; } logg("*Clamonacc: attempting to stop event consumer thread ...\n"); if (scan_queue_pid > 0) { pthread_cancel(scan_queue_pid); pthread_join(scan_queue_pid, NULL); } scan_queue_pid = 0; logg("*Clamonacc: attempting to stop ddd thread ... \n"); if (ddd_pid > 0) { pthread_cancel(ddd_pid); pthread_join(ddd_pid, NULL); } ddd_pid = 0; logg("Clamonacc: stopped\n"); onas_cleanup(g_ctx); pthread_exit(NULL); } int main(int argc, char **argv) { const struct optstruct *opts; const struct optstruct *clamdopts; struct onas_context *ctx; int ret = 0; /* Initialize context */ ctx = onas_init_context(); if (ctx == NULL) { logg("!Clamonacc: can't initialize context\n"); return 2; } /* Parse out all our command line options */ opts = optparse(NULL, argc, argv, 1, OPT_CLAMONACC, OPT_CLAMSCAN, NULL); if (opts == NULL) { logg("!Clamonacc: can't parse command line options\n"); return 2; } ctx->opts = opts; /* And our config file options */ clamdopts = optparse(optget(opts, "config-file")->strarg, 0, NULL, 1, OPT_CLAMD, 0, NULL); if (clamdopts == NULL) { logg("!Clamonacc: can't parse clamd configuration file %s\n", optget(opts, "config-file")->strarg); return 2; } ctx->clamdopts = clamdopts; /* Make sure we're good to begin spinup */ ret = startup_checks(ctx); if (ret) { goto clean_up; } #ifndef _WIN32 /* Daemonize if sanity checks are good to go */ if (!optget(ctx->opts, "foreground")->enabled) { if (-1 == daemonize()) { logg("!Clamonacc: could not daemonize\n"); return 2; } } #endif /* Setup our client */ switch (onas_setup_client(&ctx)) { case CL_SUCCESS: if (CL_SUCCESS == onas_check_client_connection(&ctx)) { break; } case CL_BREAK: ret = 0; logg("*Clamonacc: not setting up client\n"); goto clean_up; break; case CL_EARG: default: logg("!Clamonacc: can't setup client\n"); ret = 2; goto clean_up; break; } /* Setup our event queue */ ctx->maxthreads = optget(ctx->clamdopts, "OnAccessMaxThreads")->numarg; switch (onas_scan_queue_start(&ctx)) { case CL_SUCCESS: break; case CL_BREAK: case CL_EARG: case CL_ECREAT: default: ret = 2; logg("!Clamonacc: can't setup event consumer queue\n"); goto clean_up; break; } #if defined(FANOTIFY) /* Setup fanotify */ switch (onas_setup_fanotif(&ctx)) { case CL_SUCCESS: break; case CL_BREAK: ret = 0; goto clean_up; break; case CL_EARG: default: mprintf("!Clamonacc: can't setup fanotify\n"); ret = 2; goto clean_up; break; } if (ctx->ddd_enabled) { /* Setup inotify and kickoff DDD system */ switch (onas_enable_inotif_ddd(&ctx)) { case CL_SUCCESS: break; case CL_BREAK: ret = 0; goto clean_up; break; case CL_EARG: default: mprintf("!Clamonacc: can't setup fanotify\n"); ret = 2; goto clean_up; break; } } #else mprintf("!Clamonacc: currently, this application only runs on linux systems with fanotify enabled\n"); goto clean_up; #endif /* Setup signal handling */ g_ctx = ctx; onas_handle_signals(); logg("*Clamonacc: beginning event loops\n"); /* Kick off event loop(s) */ ret = onas_start_eloop(&ctx); /* Clean up */ clean_up: onas_cleanup(ctx); exit(ret); } static void onas_handle_signals() { sigset_t sigset; struct sigaction act; /* ignore all signals except SIGUSR1 */ sigfillset(&sigset); sigdelset(&sigset, SIGUSR1); 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); sigdelset(&sigset, SIGINT); sigdelset(&sigset, SIGTERM); #ifdef SIGBUS sigdelset(&sigset, SIGBUS); #endif pthread_sigmask(SIG_SETMASK, &sigset, NULL); memset(&act, 0, sizeof(struct sigaction)); act.sa_handler = onas_clamonacc_exit; sigfillset(&(act.sa_mask)); sigaction(SIGUSR2, &act, NULL); sigaction(SIGTERM, &act, NULL); sigaction(SIGSEGV, &act, NULL); sigaction(SIGINT, &act, NULL); } struct onas_context *onas_init_context(void) { struct onas_context *ctx = (struct onas_context *)cli_malloc(sizeof(struct onas_context)); if (NULL == ctx) { return NULL; } memset(ctx, 0, sizeof(struct onas_context)); return ctx; } cl_error_t onas_check_client_connection(struct onas_context **ctx) { cl_error_t err = CL_SUCCESS; /* 0 local, non-zero remote, errno set on error */ (*ctx)->isremote = onas_check_remote(ctx, &err); if (CL_SUCCESS == err) { logg("*Clamonacc: "); (*ctx)->isremote ? logg("*daemon is remote\n") : logg("*daemon is local\n"); } return err ? CL_EACCES : CL_SUCCESS; } int onas_start_eloop(struct onas_context **ctx) { int ret = 0; if (!ctx || !*ctx) { mprintf("!Clamonacc: unable to start clamonacc. (bad context)\n"); return CL_EARG; } #if defined(FANOTIFY) ret = onas_fan_eloop(ctx); #endif return ret; } static int startup_checks(struct onas_context *ctx) { #if defined(FANOTIFY) char faerr[128]; #endif int ret = 0; cl_error_t err = CL_SUCCESS; if (optget(ctx->opts, "help")->enabled) { help(); ret = 2; goto done; } if (optget(ctx->opts, "version")->enabled) { onas_print_server_version(&ctx); ret = 2; goto done; } #if defined(FANOTIFY) ctx->fan_fd = fanotify_init(FAN_CLASS_CONTENT | FAN_UNLIMITED_QUEUE | FAN_UNLIMITED_MARKS, O_LARGEFILE | O_RDONLY); if (ctx->fan_fd < 0) { logg("!Clamonacc: fanotify_init failed: %s\n", cli_strerror(errno, faerr, sizeof(faerr))); if (errno == EPERM) { logg("!Clamonacc: clamonacc must have elevated permissions ... exiting ...\n"); } ret = 2; goto done; } #endif if (curl_global_init(CURL_GLOBAL_NOTHING)) { ret = 2; goto done; } if (0 == onas_check_remote(&ctx, &err)) { if (CL_SUCCESS != err) { logg("!Clamonacc: daemon is local, but a connection could not be established\n"); ret = 2; goto done; } if (!optget(ctx->clamdopts, "OnAccessExcludeUID")->enabled && !optget(ctx->clamdopts, "OnAccessExcludeUname")->enabled && !optget(ctx->clamdopts, "OnAccessExcludeRootUID")->enabled) { logg("!Clamonacc: at least one of OnAccessExcludeUID, OnAccessExcludeUname, or OnAccessExcludeRootUID must be specified ... it is reccomended you exclude the clamd instance UID or uname to prevent infinite event scanning loops\n"); ret = 2; goto done; } } done: return ret; } void help(void) { mprintf_stdout = 1; mprintf("\n"); mprintf(" ClamAV: On Access Scanning Application and Client %s\n", get_version()); mprintf(" By The ClamAV Team: https://www.clamav.net/about.html#credits\n"); mprintf(" (C) 2019 Cisco Systems, Inc.\n"); mprintf("\n"); mprintf(" clamonacc [options] [file/directory/-]\n"); mprintf("\n"); mprintf(" --help -h Show this help\n"); mprintf(" --version -V Print version number and exit\n"); mprintf(" --verbose -v Be verbose\n"); mprintf(" --log=FILE -l FILE Save scanning output to FILE\n"); mprintf(" --foreground -F Output to foreground and do not daemonize\n"); mprintf(" --watch-list=FILE -w FILE Watch directories from FILE\n"); mprintf(" --exclude-list=FILE -e FILE Exclude directories from FILE\n"); mprintf(" --remove Remove infected files. Be careful!\n"); mprintf(" --move=DIRECTORY Move infected files into DIRECTORY\n"); mprintf(" --copy=DIRECTORY Copy infected files into DIRECTORY\n"); mprintf(" --config-file=FILE Read configuration from FILE.\n"); mprintf(" --allmatch -z Continue scanning within file after finding a match.\n"); mprintf(" --fdpass Pass filedescriptor to clamd (useful if clamd is running as a different user)\n"); mprintf(" --stream Force streaming files to clamd (for debugging and unit testing)\n"); mprintf("\n"); exit(0); } void *onas_cleanup(struct onas_context *ctx) { onas_context_cleanup(ctx); logg_close(); } void *onas_context_cleanup(struct onas_context *ctx) { close(ctx->fan_fd); optfree((struct optstruct *)ctx->opts); optfree((struct optstruct *)ctx->clamdopts); ctx->opts = NULL; ctx->clamdopts = NULL; free(ctx); }