clamonacc/clamonacc.c
1b264c0b
 /*
206dbaef
  *  Copyright (C) 2019-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
1b264c0b
  *
1fa5facc
  *  Authors: Mickey Sola
1b264c0b
  *
  *  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>
95bfaf7e
 #include <pthread.h>
 #include <signal.h>
1b264c0b
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
 #ifndef _WIN32
 #include <sys/time.h>
 #endif
 #include <time.h>
 #include <signal.h>
9e20cdf6
 #if defined(HAVE_SYS_FANOTIFY_H)
4360a991
 #include <sys/fanotify.h>
 #endif
9e20cdf6
 #include <fcntl.h>
1b264c0b
 
29389cb7
 #include <curl/curl.h>
 
9e20cdf6
 // libclamav
 #include "clamav.h"
 #include "others.h"
 
 // shared
 #include "output.h"
 #include "misc.h"
 #include "optparser.h"
 #include "actions.h"
7bc021ff
 
72c10bd1
 #include "clamonacc.h"
 #include "client/client.h"
 #include "fanotif/fanotif.h"
 #include "inotif/inotif.h"
9e20cdf6
 #include "scan/onas_queue.h"
1b264c0b
 
4fee702f
 pthread_t ddd_pid        = 0;
78b1b1b4
 pthread_t scan_queue_pid = 0;
1b264c0b
 
95bfaf7e
 static void onas_handle_signals();
16ce1990
 static int startup_checks(struct onas_context *ctx);
95bfaf7e
 static struct onas_context *g_ctx = NULL;
 
 static void onas_clamonacc_exit(int sig)
 {
4fee702f
     logg("*Clamonacc: onas_clamonacc_exit(), signal %d\n", sig);
     if (sig == 11) {
431f0232
         logg("!Clamonacc: clamonacc has experienced a fatal error, if you continue to see this error, please run clamonacc with --verbose and report the issue and crash report to the developpers\n");
4fee702f
     }
 
     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);
95bfaf7e
 }
16ce1990
 
1b264c0b
 int main(int argc, char **argv)
 {
4fee702f
     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;
 
854d38de
     if (optget(opts, "verbose")->enabled) {
         mprintf_verbose = 1;
         logg_verbose    = 1;
     }
 
4fee702f
     /* 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);
1c683bf0
         optfree((struct optstruct *)opts);
4fee702f
         return 2;
     }
     ctx->clamdopts = clamdopts;
 
     /* Make sure we're good to begin spinup */
     ret = startup_checks(ctx);
     if (ret) {
854d38de
         if (ret == (int)CL_BREAK) {
             ret = 0;
         }
         goto done;
4fee702f
     }
4cc47621
 
 #ifndef _WIN32
4fee702f
     /* 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;
4cc47621
         }
4fee702f
     }
4cc47621
 #endif
 
4fee702f
     /* Setup our client */
     switch (onas_setup_client(&ctx)) {
         case CL_SUCCESS:
             if (CL_SUCCESS == onas_check_client_connection(&ctx)) {
                 break;
             }
854d38de
             /* fall-through */
4fee702f
         case CL_BREAK:
             ret = 0;
             logg("*Clamonacc: not setting up client\n");
854d38de
             goto done;
4fee702f
             break;
         case CL_EARG:
         default:
             logg("!Clamonacc: can't setup client\n");
             ret = 2;
854d38de
             goto done;
4fee702f
             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");
854d38de
             goto done;
4fee702f
             break;
     }
 
9e20cdf6
 #if defined(HAVE_SYS_FANOTIFY_H)
4fee702f
     /* Setup fanotify */
     switch (onas_setup_fanotif(&ctx)) {
         case CL_SUCCESS:
             break;
         case CL_BREAK:
             ret = 0;
854d38de
             goto done;
4fee702f
             break;
         case CL_EARG:
         default:
             mprintf("!Clamonacc: can't setup fanotify\n");
             ret = 2;
854d38de
             goto done;
4fee702f
             break;
     }
 
     if (ctx->ddd_enabled) {
         /* Setup inotify and kickoff DDD system */
         switch (onas_enable_inotif_ddd(&ctx)) {
b365aa58
             case CL_SUCCESS:
                 break;
             case CL_BREAK:
4fee702f
                 ret = 0;
854d38de
                 goto done;
4fee702f
                 break;
b365aa58
             case CL_EARG:
             default:
4fee702f
                 mprintf("!Clamonacc: can't setup fanotify\n");
b365aa58
                 ret = 2;
854d38de
                 goto done;
b365aa58
                 break;
         }
4fee702f
     }
953a43f3
 #else
4fee702f
     mprintf("!Clamonacc: currently, this application only runs on linux systems with fanotify enabled\n");
854d38de
     goto done;
953a43f3
 #endif
1b264c0b
 
4fee702f
     /* Setup signal handling */
     g_ctx = ctx;
     onas_handle_signals();
95bfaf7e
 
4fee702f
     logg("*Clamonacc: beginning event loops\n");
     /*  Kick off event loop(s) */
     ret = onas_start_eloop(&ctx);
1b264c0b
 
854d38de
 done:
4fee702f
     /* Clean up */
     onas_cleanup(ctx);
     exit(ret);
953a43f3
 }
1b264c0b
 
4fee702f
 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
95bfaf7e
 	 * SIGFPE, SIGILL, SIGSEGV, or SIGBUS signal */
4fee702f
     sigdelset(&sigset, SIGFPE);
     sigdelset(&sigset, SIGILL);
     sigdelset(&sigset, SIGSEGV);
     sigdelset(&sigset, SIGINT);
     sigdelset(&sigset, SIGTERM);
95bfaf7e
 #ifdef SIGBUS
4fee702f
     sigdelset(&sigset, SIGBUS);
95bfaf7e
 #endif
4fee702f
     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);
95bfaf7e
 }
 
4fee702f
 struct onas_context *onas_init_context(void)
 {
     struct onas_context *ctx = (struct onas_context *)cli_malloc(sizeof(struct onas_context));
953a43f3
     if (NULL == ctx) {
         return NULL;
1b264c0b
     }
 
953a43f3
     memset(ctx, 0, sizeof(struct onas_context));
     return ctx;
 }
1b264c0b
 
4fee702f
 cl_error_t onas_check_client_connection(struct onas_context **ctx)
 {
1b264c0b
 
4fee702f
     cl_error_t err = CL_SUCCESS;
20b9d679
 
4fee702f
     /* 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;
953a43f3
 }
1b264c0b
 
4fee702f
 int onas_start_eloop(struct onas_context **ctx)
 {
     int ret = 0;
1b264c0b
 
4fee702f
     if (!ctx || !*ctx) {
         mprintf("!Clamonacc: unable to start clamonacc. (bad context)\n");
         return CL_EARG;
     }
953a43f3
 
9e20cdf6
 #if defined(HAVE_SYS_FANOTIFY_H)
4fee702f
     ret = onas_fan_eloop(ctx);
953a43f3
 #endif
 
4fee702f
     return ret;
1b264c0b
 }
 
4fee702f
 static int startup_checks(struct onas_context *ctx)
 {
9e20cdf6
 #if defined(HAVE_SYS_FANOTIFY_H)
4fee702f
     char faerr[128];
4360a991
 #endif
4fee702f
     int ret        = 0;
     cl_error_t err = CL_SUCCESS;
16ce1990
 
4fee702f
     if (optget(ctx->opts, "help")->enabled) {
         help();
         ret = 2;
         goto done;
     }
16ce1990
 
9e20cdf6
 #if defined(HAVE_SYS_FANOTIFY_H)
4fee702f
     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;
     }
4360a991
 #endif
 
29389cb7
 #if ((LIBCURL_VERSION_MAJOR < 7) || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR < 40))
     if (optget(ctx->opts, "fdpass")->enabled || !optget(ctx->clamdopts, "TCPSocket")->enabled || !optget(ctx->clamdopts, "TCPAddr")->enabled) {
9e08216c
         logg("!Clamonacc: Version of curl is too low to use fdpassing. Please use tcp socket streaming instead\n.");
         ret = 2;
         goto done;
29389cb7
     }
 #endif
 
4fee702f
     if (curl_global_init(CURL_GLOBAL_NOTHING)) {
         ret = 2;
         goto done;
     }
 
74c55ce5
     if (optget(ctx->opts, "version")->enabled) {
         onas_print_server_version(&ctx);
         ret = 2;
         goto done;
     }
 
     if (optget(ctx->opts, "ping")->enabled && !optget(ctx->opts, "wait")->enabled) {
854d38de
         int16_t ping_result = onas_ping_clamd(&ctx);
         switch (ping_result) {
             case 0:
                 ret = (int)CL_BREAK;
                 break;
             case 1:
                 ret = (int)CL_ETIMEOUT;
                 break;
             default:
                 ret = 2;
                 break;
         }
74c55ce5
         goto done;
     }
 
     if (optget(ctx->opts, "wait")->enabled) {
854d38de
         int16_t ping_result = onas_ping_clamd(&ctx);
         switch (ping_result) {
             case 0:
                 ret = (int)CL_BREAK;
                 break;
             case 1:
                 ret = (int)CL_ETIMEOUT;
                 goto done;
             default:
                 ret = 2;
                 goto done;
74c55ce5
         }
     }
 
4fee702f
     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;
         }
     }
29389cb7
 
16ce1990
 done:
4fee702f
     return ret;
16ce1990
 }
 
1b264c0b
 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");
206dbaef
     mprintf("           (C) 2020 Cisco Systems, Inc.\n");
1b264c0b
     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");
52dc3149
     mprintf("    --foreground           -F          Output to foreground and do not daemonize\n");
953a43f3
     mprintf("    --watch-list=FILE      -w FILE     Watch directories from FILE\n");
74c55ce5
     mprintf("    --exclude-list=FILE    -e FILE     Exclude directories from FILE\n");
9e08216c
     mprintf("    --ping                 -p A[:I]    Ping clamd up to [A] times at optional interval [I] until it responds.\n");
     mprintf("    --wait                 -w          Wait up to 30 seconds for clamd to start. Optionally use alongside --ping to set attempts [A] and interval [I] to check clamd.\n");
1b264c0b
     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);
 }
953a43f3
 
dcad75ff
 void onas_cleanup(struct onas_context *ctx)
4fee702f
 {
     onas_context_cleanup(ctx);
     logg_close();
953a43f3
 }
 
dcad75ff
 void onas_context_cleanup(struct onas_context *ctx)
4fee702f
 {
     close(ctx->fan_fd);
     optfree((struct optstruct *)ctx->opts);
     optfree((struct optstruct *)ctx->clamdopts);
     ctx->opts      = NULL;
     ctx->clamdopts = NULL;
     free(ctx);
953a43f3
 }