clamonacc/clamonacc.c
987f7a7d
 /*
7894638e
  *  Copyright (C) 2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
987f7a7d
  *
7894638e
  *  Authors: Mickey Sola
987f7a7d
  *
  *  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 <stdio.h>
 #include <string.h>
 #include <stdlib.h>
a02cc492
 #include <pthread.h>
 #include <signal.h>
987f7a7d
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
 #ifndef _WIN32
 #include <sys/time.h>
 #endif
 #include <time.h>
 #include <signal.h>
f972802f
 #if defined(FANOTIFY)
 #include <sys/fanotify.h>
 #include <fcntl.h>
 #endif
987f7a7d
 
56426d28
 #include "libclamav/clamav.h"
 #include "libclamav/others.h"
 #include "shared/output.h"
 #include "shared/misc.h"
 #include "shared/optparser.h"
 #include "shared/actions.h"
fe7a6972
 
56426d28
 #include "clamonacc.h"
 #include "client/client.h"
 #include "fanotif/fanotif.h"
 #include "inotif/inotif.h"
 #include "scan/queue.h"
987f7a7d
 
43422579
 pthread_t ddd_pid        = 0;
ad49ff53
 pthread_t scan_queue_pid = 0;
987f7a7d
 
a02cc492
 static void onas_handle_signals();
a3ae175e
 static int startup_checks(struct onas_context *ctx);
a02cc492
 static struct onas_context *g_ctx = NULL;
 
 static void onas_clamonacc_exit(int sig)
 {
43422579
     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);
a02cc492
 }
a3ae175e
 
987f7a7d
 int main(int argc, char **argv)
 {
43422579
     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;
     }
b49436ff
 
 #ifndef _WIN32
43422579
     /* 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;
b49436ff
         }
43422579
     }
b49436ff
 #endif
 
43422579
     /* 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)) {
7039a5ea
             case CL_SUCCESS:
                 break;
             case CL_BREAK:
43422579
                 ret = 0;
                 goto clean_up;
                 break;
7039a5ea
             case CL_EARG:
             default:
43422579
                 mprintf("!Clamonacc: can't setup fanotify\n");
7039a5ea
                 ret = 2;
                 goto clean_up;
                 break;
         }
43422579
     }
01456705
 #else
43422579
     mprintf("!Clamonacc: currently, this application only runs on linux systems with fanotify enabled\n");
     goto clean_up;
01456705
 #endif
987f7a7d
 
43422579
     /* Setup signal handling */
     g_ctx = ctx;
     onas_handle_signals();
a02cc492
 
43422579
     logg("*Clamonacc: beginning event loops\n");
     /*  Kick off event loop(s) */
     ret = onas_start_eloop(&ctx);
987f7a7d
 
43422579
     /* Clean up */
01456705
 clean_up:
43422579
     onas_cleanup(ctx);
     exit(ret);
01456705
 }
987f7a7d
 
43422579
 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
a02cc492
 	 * SIGFPE, SIGILL, SIGSEGV, or SIGBUS signal */
43422579
     sigdelset(&sigset, SIGFPE);
     sigdelset(&sigset, SIGILL);
     sigdelset(&sigset, SIGSEGV);
     sigdelset(&sigset, SIGINT);
     sigdelset(&sigset, SIGTERM);
a02cc492
 #ifdef SIGBUS
43422579
     sigdelset(&sigset, SIGBUS);
a02cc492
 #endif
43422579
     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);
a02cc492
 }
 
43422579
 struct onas_context *onas_init_context(void)
 {
     struct onas_context *ctx = (struct onas_context *)cli_malloc(sizeof(struct onas_context));
01456705
     if (NULL == ctx) {
         return NULL;
987f7a7d
     }
 
01456705
     memset(ctx, 0, sizeof(struct onas_context));
     return ctx;
 }
987f7a7d
 
43422579
 cl_error_t onas_check_client_connection(struct onas_context **ctx)
 {
987f7a7d
 
43422579
     cl_error_t err = CL_SUCCESS;
424c912b
 
43422579
     /* 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;
01456705
 }
987f7a7d
 
43422579
 int onas_start_eloop(struct onas_context **ctx)
 {
     int ret = 0;
987f7a7d
 
43422579
     if (!ctx || !*ctx) {
         mprintf("!Clamonacc: unable to start clamonacc. (bad context)\n");
         return CL_EARG;
     }
01456705
 
fe7a6972
 #if defined(FANOTIFY)
43422579
     ret = onas_fan_eloop(ctx);
01456705
 #endif
 
43422579
     return ret;
987f7a7d
 }
 
43422579
 static int startup_checks(struct onas_context *ctx)
 {
a3ae175e
 
f972802f
 #if defined(FANOTIFY)
43422579
     char faerr[128];
f972802f
 #endif
43422579
     int ret        = 0;
     cl_error_t err = CL_SUCCESS;
a3ae175e
 
43422579
     if (optget(ctx->opts, "help")->enabled) {
         help();
         ret = 2;
         goto done;
     }
a3ae175e
 
43422579
     if (optget(ctx->opts, "version")->enabled) {
         onas_print_server_version(&ctx);
         ret = 2;
         goto done;
     }
a3ae175e
 
f972802f
 #if defined(FANOTIFY)
43422579
     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;
     }
f972802f
 #endif
 
43422579
     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;
         }
     }
a3ae175e
 done:
43422579
     return ret;
a3ae175e
 }
 
987f7a7d
 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");
7894638e
     mprintf("           (C) 2019 Cisco Systems, Inc.\n");
987f7a7d
     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");
9707132c
     mprintf("    --foreground           -F          Output to foreground and do not daemonize\n");
01456705
     mprintf("    --watch-list=FILE      -w FILE     Watch directories from FILE\n");
d18a2a67
     mprintf("    --exclude-list=FILE   -e FILE     Exclude directories from FILE\n");
987f7a7d
     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);
 }
01456705
 
43422579
 void *onas_cleanup(struct onas_context *ctx)
 {
     onas_context_cleanup(ctx);
     logg_close();
01456705
 }
 
43422579
 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);
01456705
 }