1 /*
2  *  Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3  *  Copyright (C) 2007-2013 Sourcefire, Inc.
4  *
5  *  Authors: Tomasz Kojm
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  *  MA 02110-1301, USA.
20  *
21  */
22 
23 #if HAVE_CONFIG_H
24 #include "clamav-config.h"
25 #endif
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <ctype.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #ifdef HAVE_PWD_H
34 #include <pwd.h>
35 #endif
36 #include <dirent.h>
37 #ifndef _WIN32
38 #include <sys/wait.h>
39 #include <utime.h>
40 #include <sys/time.h>
41 #include <sys/resource.h>
42 #endif
43 #include <fcntl.h>
44 #ifdef HAVE_UNISTD_H
45 #include <unistd.h>
46 #endif
47 #include <sys/types.h>
48 #include <signal.h>
49 #include <errno.h>
50 #include <math.h>
51 
52 // libclamav
53 #include "clamav.h"
54 #include "others.h"
55 #include "matcher-ac.h"
56 #include "matcher-pcre.h"
57 #include "str.h"
58 #include "readdb.h"
59 
60 // common
61 #include "optparser.h"
62 #include "actions.h"
63 #include "output.h"
64 #include "misc.h"
65 
66 #include "manager.h"
67 #include "global.h"
68 
69 #ifdef C_LINUX
70 dev_t procdev;
71 #endif
72 
73 #ifdef _WIN32
74 /* FIXME: If possible, handle users correctly */
checkaccess(const char * path,const char * username,int mode)75 static int checkaccess(const char *path, const char *username, int mode)
76 {
77     return !access(path, mode);
78 }
79 #else
checkaccess(const char * path,const char * username,int mode)80 static int checkaccess(const char *path, const char *username, int mode)
81 {
82     struct passwd *user;
83     int ret = 0, status;
84 
85     if (!geteuid()) {
86         if ((user = getpwnam(username)) == NULL) {
87             return -1;
88         }
89 
90         switch (fork()) {
91             case -1:
92                 return -2;
93             case 0:
94                 if (setgid(user->pw_gid)) {
95                     fprintf(stderr, "ERROR: setgid(%d) failed.\n", (int)user->pw_gid);
96                     exit(0);
97                 }
98 
99                 if (setuid(user->pw_uid)) {
100                     fprintf(stderr, "ERROR: setuid(%d) failed.\n", (int)user->pw_uid);
101                     exit(0);
102                 }
103 
104                 if (access(path, mode))
105                     exit(0);
106                 else
107                     exit(1);
108             default:
109                 wait(&status);
110                 if (WIFEXITED(status) && WEXITSTATUS(status) == 1)
111                     ret = 1;
112         }
113     } else {
114         if (!access(path, mode))
115             ret = 1;
116     }
117 
118     return ret;
119 }
120 #endif
121 
122 struct metachain {
123     char **chains;
124     size_t lastadd;
125     size_t lastvir;
126     size_t level;
127     size_t nchains;
128 };
129 
130 struct clamscan_cb_data {
131     struct metachain *chain;
132     const char *filename;
133 };
134 
pre(int fd,const char * type,void * context)135 static cl_error_t pre(int fd, const char *type, void *context)
136 {
137     struct metachain *c;
138     struct clamscan_cb_data *d;
139 
140     UNUSEDPARAM(fd);
141     UNUSEDPARAM(type);
142 
143     if (!(context))
144         return CL_CLEAN;
145     d = (struct clamscan_cb_data *)context;
146     c = d->chain;
147     if (c == NULL)
148         return CL_CLEAN;
149 
150     c->level++;
151 
152     return CL_CLEAN;
153 }
154 
print_chain(struct metachain * c,char * str,size_t len)155 static int print_chain(struct metachain *c, char *str, size_t len)
156 {
157     size_t i;
158     size_t na = 0;
159 
160     for (i = 0; i < c->nchains - 1; i++) {
161         size_t n = strlen(c->chains[i]);
162 
163         if (na)
164             str[na++] = '!';
165 
166         if (n + na + 2 > len)
167             break;
168 
169         memcpy(str + na, c->chains[i], n);
170         na += n;
171     }
172 
173     str[na]      = '\0';
174     str[len - 1] = '\0';
175 
176     return i == c->nchains - 1 ? 0 : 1;
177 }
178 
post(int fd,int result,const char * virname,void * context)179 static cl_error_t post(int fd, int result, const char *virname, void *context)
180 {
181     struct clamscan_cb_data *d = context;
182     struct metachain *c        = NULL;
183     char str[128];
184 
185     UNUSEDPARAM(fd);
186     UNUSEDPARAM(result);
187 
188     if (d != NULL)
189         c = d->chain;
190 
191     if (c && c->nchains) {
192         print_chain(c, str, sizeof(str));
193 
194         if (c->level == c->lastadd && !virname)
195             free(c->chains[--c->nchains]);
196 
197         if (virname && !c->lastvir)
198             c->lastvir = c->level;
199     }
200 
201     if (c)
202         c->level--;
203 
204     return CL_CLEAN;
205 }
206 
meta(const char * container_type,unsigned long fsize_container,const char * filename,unsigned long fsize_real,int is_encrypted,unsigned int filepos_container,void * context)207 static cl_error_t meta(const char *container_type, unsigned long fsize_container, const char *filename,
208                        unsigned long fsize_real, int is_encrypted, unsigned int filepos_container, void *context)
209 {
210     char prev[128];
211     struct metachain *c;
212     struct clamscan_cb_data *d;
213     const char *type;
214     size_t n;
215     char *chain;
216     char **chains;
217     int toolong;
218 
219     UNUSEDPARAM(fsize_container);
220     UNUSEDPARAM(fsize_real);
221     UNUSEDPARAM(is_encrypted);
222     UNUSEDPARAM(filepos_container);
223 
224     if (!(context))
225         return CL_CLEAN;
226     d = (struct clamscan_cb_data *)context;
227 
228     c    = d->chain;
229     type = (strncmp(container_type, "CL_TYPE_", 8) == 0 ? container_type + 8 : container_type);
230     n    = strlen(type) + strlen(filename) + 2;
231 
232     if (!c)
233         return CL_CLEAN;
234 
235     chain = malloc(n);
236 
237     if (!chain)
238         return CL_CLEAN;
239 
240     if (!strcmp(type, "ANY"))
241         snprintf(chain, n, "%s", filename);
242     else
243         snprintf(chain, n, "%s:%s", type, filename);
244 
245     if (c->lastadd != c->level) {
246         n = c->nchains + 1;
247 
248         chains = realloc(c->chains, n * sizeof(*chains));
249         if (!chains) {
250             free(chain);
251             return CL_CLEAN;
252         }
253 
254         c->chains  = chains;
255         c->nchains = n;
256         c->lastadd = c->level;
257     } else {
258         if (c->nchains > 0)
259             free(c->chains[c->nchains - 1]);
260     }
261 
262     if (c->nchains > 0) {
263         c->chains[c->nchains - 1] = chain;
264         toolong                   = print_chain(c, prev, sizeof(prev));
265         logg("*Scanning %s%s!%s\n", prev, toolong ? "..." : "", chain);
266     } else {
267         free(chain);
268     }
269 
270     return CL_CLEAN;
271 }
272 
clamscan_virus_found_cb(int fd,const char * virname,void * context)273 static void clamscan_virus_found_cb(int fd, const char *virname, void *context)
274 {
275     struct clamscan_cb_data *data = (struct clamscan_cb_data *)context;
276     const char *filename;
277 
278     UNUSEDPARAM(fd);
279 
280     if (data == NULL)
281         return;
282     if (data->filename != NULL)
283         filename = data->filename;
284     else
285         filename = "(filename not set)";
286     logg("~%s: %s FOUND\n", filename, virname);
287     return;
288 }
289 
scanfile(const char * filename,struct cl_engine * engine,const struct optstruct * opts,struct cl_scan_options * options)290 static void scanfile(const char *filename, struct cl_engine *engine, const struct optstruct *opts, struct cl_scan_options *options)
291 {
292     cl_error_t ret = CL_SUCCESS;
293     int fd, included;
294     unsigned i;
295     const struct optstruct *opt;
296     const char *virname = NULL;
297     STATBUF sb;
298     struct metachain chain;
299     struct clamscan_cb_data data;
300 
301     char *real_filename = NULL;
302 
303     if (NULL == filename || NULL == engine || NULL == opts || NULL == options) {
304         logg("scanfile: Invalid args.\n");
305         ret = CL_EARG;
306         goto done;
307     }
308 
309     ret = cli_realpath((const char *)filename, &real_filename);
310     if (CL_SUCCESS != ret) {
311         logg("*Failed to determine real filename of %s.\n", filename);
312         logg("*Quarantine of the file may fail if file path contains symlinks.\n");
313     } else {
314         filename = real_filename;
315     }
316 
317     if ((opt = optget(opts, "exclude"))->enabled) {
318         while (opt) {
319             if (match_regex(filename, opt->strarg) == 1) {
320                 if (!printinfected)
321                     logg("~%s: Excluded\n", filename);
322 
323                 goto done;
324             }
325 
326             opt = opt->nextarg;
327         }
328     }
329 
330     if ((opt = optget(opts, "include"))->enabled) {
331         included = 0;
332 
333         while (opt) {
334             if (match_regex(filename, opt->strarg) == 1) {
335                 included = 1;
336                 break;
337             }
338 
339             opt = opt->nextarg;
340         }
341 
342         if (!included) {
343             if (!printinfected)
344                 logg("~%s: Excluded\n", filename);
345 
346             goto done;
347         }
348     }
349 
350     /* argh, don't scan /proc files */
351     if (CLAMSTAT(filename, &sb) != -1) {
352 #ifdef C_LINUX
353         if (procdev && sb.st_dev == procdev) {
354             if (!printinfected)
355                 logg("~%s: Excluded (/proc)\n", filename);
356 
357             goto done;
358         }
359 #endif
360         if (!sb.st_size) {
361             if (!printinfected)
362                 logg("~%s: Empty file\n", filename);
363 
364             goto done;
365         }
366 
367         info.rblocks += sb.st_size / CL_COUNT_PRECISION;
368     }
369 
370 #ifndef _WIN32
371     if (geteuid()) {
372         if (checkaccess(filename, NULL, R_OK) != 1) {
373             if (!printinfected)
374                 logg("~%s: Access denied\n", filename);
375 
376             info.errors++;
377             goto done;
378         }
379     }
380 #endif
381 
382     memset(&chain, 0, sizeof(chain));
383     if (optget(opts, "archive-verbose")->enabled) {
384         chain.chains = malloc(sizeof(char **));
385         if (chain.chains) {
386             chain.chains[0] = strdup(filename);
387             if (!chain.chains[0]) {
388                 free(chain.chains);
389                 logg("Unable to allocate memory in scanfile()\n");
390                 info.errors++;
391                 goto done;
392             }
393             chain.nchains = 1;
394         }
395     }
396 
397     logg("*Scanning %s\n", filename);
398 
399     if ((fd = safe_open(filename, O_RDONLY | O_BINARY)) == -1) {
400         logg("^Can't open file %s: %s\n", filename, strerror(errno));
401         info.errors++;
402         goto done;
403     }
404 
405     data.chain    = &chain;
406     data.filename = filename;
407     if ((ret = cl_scandesc_callback(fd, filename, &virname, &info.blocks, engine, options, &data)) == CL_VIRUS) {
408         if (optget(opts, "archive-verbose")->enabled) {
409             if (chain.nchains > 1) {
410                 char str[128];
411                 int toolong = print_chain(&chain, str, sizeof(str));
412 
413                 logg("~%s%s!(%llu)%s: %s FOUND\n", str, toolong ? "..." : "", (long long unsigned)(chain.lastvir - 1), chain.chains[chain.nchains - 1], virname);
414             } else if (chain.lastvir) {
415                 logg("~%s!(%llu): %s FOUND\n", filename, (long long unsigned)(chain.lastvir - 1), virname);
416             }
417         }
418         info.files++;
419         info.ifiles++;
420 
421         if (bell)
422             fprintf(stderr, "\007");
423     } else if (ret == CL_CLEAN) {
424         if (!printinfected && printclean)
425             mprintf("~%s: OK\n", filename);
426 
427         info.files++;
428     } else {
429         if (!printinfected)
430             logg("~%s: %s ERROR\n", filename, cl_strerror(ret));
431 
432         info.errors++;
433     }
434 
435     for (i = 0; i < chain.nchains; i++)
436         free(chain.chains[i]);
437 
438     free(chain.chains);
439     close(fd);
440 
441     if (ret == CL_VIRUS && action)
442         action(filename);
443 
444 done:
445     if (NULL != real_filename) {
446         free(real_filename);
447     }
448     return;
449 }
450 
scandirs(const char * dirname,struct cl_engine * engine,const struct optstruct * opts,struct cl_scan_options * options,unsigned int depth,dev_t dev)451 static void scandirs(const char *dirname, struct cl_engine *engine, const struct optstruct *opts, struct cl_scan_options *options, unsigned int depth, dev_t dev)
452 {
453     DIR *dd;
454     struct dirent *dent;
455     STATBUF sb;
456     char *fname;
457     int included;
458     const struct optstruct *opt;
459     unsigned int dirlnk, filelnk;
460 
461     if ((opt = optget(opts, "exclude-dir"))->enabled) {
462         while (opt) {
463             if (match_regex(dirname, opt->strarg) == 1) {
464                 if (!printinfected)
465                     logg("~%s: Excluded\n", dirname);
466 
467                 return;
468             }
469 
470             opt = opt->nextarg;
471         }
472     }
473 
474     if ((opt = optget(opts, "include-dir"))->enabled) {
475         included = 0;
476         while (opt) {
477             if (match_regex(dirname, opt->strarg) == 1) {
478                 included = 1;
479                 break;
480             }
481 
482             opt = opt->nextarg;
483         }
484 
485         if (!included) {
486             if (!printinfected)
487                 logg("~%s: Excluded\n", dirname);
488 
489             return;
490         }
491     }
492 
493     if (depth > (unsigned int)optget(opts, "max-dir-recursion")->numarg)
494         return;
495 
496     dirlnk  = optget(opts, "follow-dir-symlinks")->numarg;
497     filelnk = optget(opts, "follow-file-symlinks")->numarg;
498 
499     if ((dd = opendir(dirname)) != NULL) {
500         info.dirs++;
501         depth++;
502         while ((dent = readdir(dd))) {
503             if (dent->d_ino) {
504                 if (strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..")) {
505                     /* build the full name */
506                     fname = malloc(strlen(dirname) + strlen(dent->d_name) + 2);
507                     if (fname == NULL) { /* oops, malloc() failed, print warning and return */
508                         logg("!scandirs: Memory allocation failed for fname\n");
509                         break;
510                     }
511 
512                     if (!strcmp(dirname, PATHSEP))
513                         sprintf(fname, PATHSEP "%s", dent->d_name);
514                     else
515                         sprintf(fname, "%s" PATHSEP "%s", dirname, dent->d_name);
516 
517                     /* stat the file */
518                     if (LSTAT(fname, &sb) != -1) {
519                         if (!optget(opts, "cross-fs")->enabled) {
520                             if (sb.st_dev != dev) {
521                                 if (!printinfected)
522                                     logg("~%s: Excluded\n", fname);
523 
524                                 free(fname);
525                                 continue;
526                             }
527                         }
528                         if (S_ISLNK(sb.st_mode)) {
529                             if (dirlnk != 2 && filelnk != 2) {
530                                 if (!printinfected)
531                                     logg("%s: Symbolic link\n", fname);
532                             } else if (CLAMSTAT(fname, &sb) != -1) {
533                                 if (S_ISREG(sb.st_mode) && filelnk == 2) {
534                                     scanfile(fname, engine, opts, options);
535                                 } else if (S_ISDIR(sb.st_mode) && dirlnk == 2) {
536                                     if (recursion)
537                                         scandirs(fname, engine, opts, options, depth, dev);
538                                 } else {
539                                     if (!printinfected)
540                                         logg("%s: Symbolic link\n", fname);
541                                 }
542                             }
543                         } else if (S_ISREG(sb.st_mode)) {
544                             scanfile(fname, engine, opts, options);
545                         } else if (S_ISDIR(sb.st_mode) && recursion) {
546                             scandirs(fname, engine, opts, options, depth, dev);
547                         }
548                     }
549 
550                     free(fname);
551                 }
552             }
553         }
554         closedir(dd);
555     } else {
556         if (!printinfected)
557             logg("~%s: Can't open directory.\n", dirname);
558 
559         info.errors++;
560     }
561 }
562 
scanstdin(const struct cl_engine * engine,struct cl_scan_options * options)563 static int scanstdin(const struct cl_engine *engine, struct cl_scan_options *options)
564 {
565     int ret;
566     unsigned int fsize  = 0;
567     const char *virname = NULL;
568     const char *tmpdir  = NULL;
569     char *file, buff[FILEBUFF];
570     size_t bread;
571     FILE *fs;
572     struct clamscan_cb_data data;
573 
574     tmpdir = cl_engine_get_str(engine, CL_ENGINE_TMPDIR, NULL);
575     if (NULL == tmpdir) {
576         tmpdir = cli_gettmpdir();
577     }
578 
579     if (access(tmpdir, R_OK | W_OK) == -1) {
580         logg("!Can't write to temporary directory\n");
581         return 2;
582     }
583 
584     if (!(file = cli_gentemp(tmpdir))) {
585         logg("!Can't generate tempfile name\n");
586         return 2;
587     }
588 
589     if (!(fs = fopen(file, "wb"))) {
590         logg("!Can't open %s for writing\n", file);
591         free(file);
592         return 2;
593     }
594 
595     while ((bread = fread(buff, 1, FILEBUFF, stdin))) {
596         fsize += bread;
597         if (fwrite(buff, 1, bread, fs) < bread) {
598             logg("!Can't write to %s\n", file);
599             free(file);
600             fclose(fs);
601             return 2;
602         }
603     }
604 
605     fclose(fs);
606 
607     logg("*Checking %s\n", file);
608 
609     info.files++;
610     info.rblocks += fsize / CL_COUNT_PRECISION;
611 
612     data.filename = "stdin";
613     data.chain    = NULL;
614     if ((ret = cl_scanfile_callback(file, &virname, &info.blocks, engine, options, &data)) == CL_VIRUS) {
615         info.ifiles++;
616 
617         if (bell)
618             fprintf(stderr, "\007");
619     } else if (ret == CL_CLEAN) {
620         if (!printinfected)
621             mprintf("stdin: OK\n");
622     } else {
623         if (!printinfected)
624             logg("stdin: %s ERROR\n", cl_strerror(ret));
625 
626         info.errors++;
627     }
628 
629     unlink(file);
630     free(file);
631     return ret;
632 }
633 
634 struct sigload_progress {
635     time_t startTime;
636     time_t lastRunTime;
637     uint8_t bComplete;
638 };
639 
640 struct engine_compile_progress {
641     time_t startTime;
642     time_t lastRunTime;
643     uint8_t bComplete;
644 };
645 
646 struct engine_free_progress {
647     time_t startTime;
648     time_t lastRunTime;
649     uint8_t bComplete;
650 };
651 
print_time(time_t seconds)652 static void print_time(time_t seconds)
653 {
654     if (seconds >= 3600) {
655         fprintf(stdout, "%2lldh %02lldm", (long long)seconds / 3600, ((long long)seconds % 3600) / 60);
656     } else if (seconds >= 60) {
657         fprintf(stdout, "%2lldm %02llds", (long long)seconds / 60, (long long)seconds % 60);
658     } else {
659         fprintf(stdout, "%3llds", (long long)seconds);
660     }
661 }
662 
print_num_sigs(size_t sigs,int bPad)663 static void print_num_sigs(size_t sigs, int bPad)
664 {
665     if (sigs >= (1000 * 1000)) {
666         const char *format = bPad ? "%7.02fM" : "%.02fM";
667         double megasigs    = sigs / (double)(1000 * 1000);
668         fprintf(stdout, format, megasigs);
669     } else if (sigs >= 1000) {
670         const char *format = bPad ? "%7.02fK" : "%.02fK";
671         double kilosigs    = sigs / (double)(1000);
672         fprintf(stdout, format, kilosigs);
673     } else {
674         const char *format = bPad ? "%8zu" : "%zu";
675         fprintf(stdout, format, sigs);
676     }
677 }
678 
679 /**
680  * @brief Progress callback for sig-load
681  *
682  * @param total_items   Total number of items
683  * @param now_completed Number of items completed
684  * @param context       Opaque application provided data; This maps to sigload_progress
685  */
sigload_callback(size_t total_items,size_t now_completed,void * context)686 static cl_error_t sigload_callback(size_t total_items, size_t now_completed, void *context)
687 {
688     time_t curtime = 0;
689     time_t remtime = 0;
690 
691     struct sigload_progress *sigloadProgress = (struct sigload_progress *)context;
692 
693     uint32_t i             = 0;
694     uint32_t totalNumDots  = 25;
695     uint32_t numDots       = 0;
696     double fraction_loaded = 0.0;
697 
698     if ((total_items <= 0) || (sigloadProgress->bComplete)) {
699         return CL_SUCCESS;
700     }
701 
702     fraction_loaded = (double)now_completed / (double)total_items;
703     numDots         = round(fraction_loaded * totalNumDots);
704 
705     if (0 == sigloadProgress->startTime) {
706         sigloadProgress->startTime = time(0);
707     }
708     curtime = time(0) - sigloadProgress->startTime;
709 
710     sigloadProgress->lastRunTime = curtime;
711 
712 #ifndef _WIN32
713     fprintf(stdout, "\e[?7l");
714 #endif
715     if (fraction_loaded <= 0.0) {
716         fprintf(stdout, "Loading:   ");
717         print_time(curtime);
718         fprintf(stdout, "               ");
719     } else {
720         remtime = (curtime / fraction_loaded) - curtime;
721         fprintf(stdout, "Loading:   ");
722         print_time(curtime);
723         fprintf(stdout, ", ETA: ");
724         print_time(remtime);
725         fprintf(stdout, " ");
726     }
727 
728     fprintf(stdout, "[");
729     if (numDots > 0) {
730         if (numDots > 1) {
731             for (i = 0; i < numDots - 1; i++) {
732                 fprintf(stdout, "=");
733             }
734         }
735         fprintf(stdout, ">");
736         i++;
737     }
738     for (; i < totalNumDots; i++) {
739         fprintf(stdout, " ");
740     }
741 
742     fprintf(stdout, "] ");
743 
744     print_num_sigs(now_completed, 1);
745     fprintf(stdout, "/");
746     print_num_sigs(total_items, 0);
747     fprintf(stdout, " sigs    ");
748 
749     if (now_completed < total_items) {
750         fprintf(stdout, "\r");
751     } else {
752         fprintf(stdout, "\n");
753         sigloadProgress->bComplete = 1;
754     }
755 #ifndef _WIN32
756     fprintf(stdout, "\e[?7h");
757 #endif
758     fflush(stdout);
759 
760     return CL_SUCCESS;
761 }
762 
763 /**
764  * @brief Progress callback for sig-load
765  *
766  * @param total_items   Total number of items
767  * @param now_completed Number of items completed
768  * @param context       Opaque application provided data; This maps to engine_compile_progress
769  */
engine_compile_callback(size_t total_items,size_t now_completed,void * context)770 static cl_error_t engine_compile_callback(size_t total_items, size_t now_completed, void *context)
771 {
772     time_t curtime = 0;
773     time_t remtime = 0;
774 
775     struct engine_compile_progress *engineCompileProgress = (struct engine_compile_progress *)context;
776 
777     uint32_t i               = 0;
778     uint32_t totalNumDots    = 25;
779     uint32_t numDots         = 0;
780     double fraction_compiled = 0.0;
781 
782     if ((total_items <= 0) || (engineCompileProgress->bComplete)) {
783         return CL_SUCCESS;
784     }
785 
786     fraction_compiled = (double)now_completed / (double)total_items;
787     numDots           = round(fraction_compiled * totalNumDots);
788 
789     if (0 == engineCompileProgress->startTime) {
790         engineCompileProgress->startTime = time(0);
791     }
792     curtime = time(0) - engineCompileProgress->startTime;
793 
794     engineCompileProgress->lastRunTime = curtime;
795 
796 #ifndef _WIN32
797     fprintf(stdout, "\e[?7l");
798 #endif
799     if (fraction_compiled <= 0.0) {
800         fprintf(stdout, "Compiling: ");
801         print_time(curtime);
802         fprintf(stdout, "               ");
803     } else {
804         remtime = (curtime / fraction_compiled) - curtime;
805         fprintf(stdout, "Compiling: ");
806         print_time(curtime);
807         fprintf(stdout, ", ETA: ");
808         print_time(remtime);
809         fprintf(stdout, " ");
810     }
811 
812     fprintf(stdout, "[");
813     if (numDots > 0) {
814         if (numDots > 1) {
815             for (i = 0; i < numDots - 1; i++) {
816                 fprintf(stdout, "=");
817             }
818         }
819         fprintf(stdout, ">");
820         i++;
821     }
822     for (; i < totalNumDots; i++) {
823         fprintf(stdout, " ");
824     }
825 
826     fprintf(stdout, "] ");
827 
828     print_num_sigs(now_completed, 1);
829     fprintf(stdout, "/");
830     print_num_sigs(total_items, 0);
831     fprintf(stdout, " tasks ");
832 
833     if (now_completed < total_items) {
834         fprintf(stdout, "\r");
835     } else {
836         fprintf(stdout, "\n");
837         engineCompileProgress->bComplete = 1;
838     }
839 #ifndef _WIN32
840     fprintf(stdout, "\e[?7h");
841 #endif
842     fflush(stdout);
843 
844     return CL_SUCCESS;
845 }
846 
847 #ifdef ENABLE_ENGINE_FREE_PROGRESSBAR
848 /**
849  * @brief Progress callback for sig-load
850  *
851  * @param total_items   Total number of items
852  * @param now_completed Number of items completed
853  * @param context       Opaque application provided data; This maps to engine_free_progress
854  */
engine_free_callback(size_t total_items,size_t now_completed,void * context)855 static cl_error_t engine_free_callback(size_t total_items, size_t now_completed, void *context)
856 {
857     time_t curtime = 0;
858 
859     struct engine_free_progress *engineFreeProgress = (struct engine_free_progress *)context;
860 
861     uint32_t i            = 0;
862     uint32_t totalNumDots = 10;
863     uint32_t numDots      = 0;
864     double fraction_freed = 0.0;
865 
866     if ((total_items <= 0) || (engineFreeProgress->bComplete)) {
867         return CL_SUCCESS;
868     }
869 
870     fraction_freed = (double)now_completed / (double)total_items;
871     numDots        = round(fraction_freed * totalNumDots);
872 
873     if (0 == engineFreeProgress->startTime) {
874         engineFreeProgress->startTime = time(0);
875     }
876     curtime = time(0) - engineFreeProgress->startTime;
877 
878     engineFreeProgress->lastRunTime = curtime;
879 
880 #ifndef _WIN32
881     fprintf(stdout, "\e[?7l");
882 #endif
883     fprintf(stdout, "Unloading");
884 
885     if (numDots > 0) {
886         if (numDots > 1) {
887             for (i = 0; i < numDots - 1; i++) {
888                 fprintf(stdout, ".");
889             }
890         }
891         i++;
892     }
893     for (; i < totalNumDots; i++) {
894         fprintf(stdout, " ");
895     }
896 
897     fprintf(stdout, " ");
898 
899     print_num_sigs(now_completed, 1);
900     fprintf(stdout, "/");
901     print_num_sigs(total_items, 0);
902     fprintf(stdout, " tasks ");
903 
904     if (now_completed < total_items) {
905         fprintf(stdout, "\r");
906     } else {
907         fprintf(stdout, "\n");
908         engineFreeProgress->bComplete = 1;
909     }
910 #ifndef _WIN32
911     fprintf(stdout, "\e[?7h");
912 #endif
913     fflush(stdout);
914 
915     return CL_SUCCESS;
916 }
917 #endif
918 
scanmanager(const struct optstruct * opts)919 int scanmanager(const struct optstruct *opts)
920 {
921     int ret = 0, i;
922     struct cl_scan_options options;
923     unsigned int dboptions = 0, dirlnk = 1, filelnk = 1;
924     struct cl_engine *engine = NULL;
925     STATBUF sb;
926     char *file, cwd[1024], *pua_cats = NULL;
927     const char *filename;
928     const struct optstruct *opt;
929 #ifndef _WIN32
930     struct rlimit rlim;
931 #endif
932     struct sigload_progress sigload_progress_ctx               = {0};
933     struct engine_compile_progress engine_compile_progress_ctx = {0};
934 #ifdef ENABLE_ENGINE_FREE_PROGRESSBAR
935     struct engine_free_progress engine_free_progress_ctx = {0};
936 #endif
937 
938     /* Initalize scan options struct */
939     memset(&options, 0, sizeof(struct cl_scan_options));
940 
941     dirlnk = optget(opts, "follow-dir-symlinks")->numarg;
942     if (dirlnk > 2) {
943         logg("!--follow-dir-symlinks: Invalid argument\n");
944         ret = 2;
945         goto done;
946     }
947 
948     filelnk = optget(opts, "follow-file-symlinks")->numarg;
949     if (filelnk > 2) {
950         logg("!--follow-file-symlinks: Invalid argument\n");
951         ret = 2;
952         goto done;
953     }
954 
955     if (optget(opts, "yara-rules")->enabled) {
956         char *p = optget(opts, "yara-rules")->strarg;
957         if (strcmp(p, "yes")) {
958             if (!strcmp(p, "only"))
959                 dboptions |= CL_DB_YARA_ONLY;
960             else if (!strcmp(p, "no"))
961                 dboptions |= CL_DB_YARA_EXCLUDE;
962         }
963     }
964 
965     if (optget(opts, "phishing-sigs")->enabled)
966         dboptions |= CL_DB_PHISHING;
967 
968     if (optget(opts, "official-db-only")->enabled)
969         dboptions |= CL_DB_OFFICIAL_ONLY;
970 
971     if (optget(opts, "phishing-scan-urls")->enabled)
972         dboptions |= CL_DB_PHISHING_URLS;
973 
974     if (optget(opts, "bytecode")->enabled)
975         dboptions |= CL_DB_BYTECODE;
976 
977     if ((ret = cl_init(CL_INIT_DEFAULT))) {
978         logg("!Can't initialize libclamav: %s\n", cl_strerror(ret));
979         ret = 2;
980         goto done;
981     }
982 
983     if (!(engine = cl_engine_new())) {
984         logg("!Can't initialize antivirus engine\n");
985         ret = 2;
986         goto done;
987     }
988 
989     cl_engine_set_clcb_virus_found(engine, clamscan_virus_found_cb);
990 
991     if (isatty(fileno(stdout)) &&
992         !optget(opts, "debug")->enabled &&
993         !optget(opts, "quiet")->enabled &&
994         !optget(opts, "infected")->enabled &&
995         !optget(opts, "no-summary")->enabled) {
996         /* set progress callbacks */
997         cl_engine_set_clcb_sigload_progress(engine, sigload_callback, &sigload_progress_ctx);
998         cl_engine_set_clcb_engine_compile_progress(engine, engine_compile_callback, &engine_compile_progress_ctx);
999 #ifdef ENABLE_ENGINE_FREE_PROGRESSBAR
1000         cl_engine_set_clcb_engine_free_progress(engine, engine_free_callback, &engine_free_progress_ctx);
1001 #endif
1002     }
1003 
1004     if (optget(opts, "disable-cache")->enabled)
1005         cl_engine_set_num(engine, CL_ENGINE_DISABLE_CACHE, 1);
1006 
1007     if (optget(opts, "detect-pua")->enabled) {
1008         dboptions |= CL_DB_PUA;
1009         if ((opt = optget(opts, "exclude-pua"))->enabled) {
1010             dboptions |= CL_DB_PUA_EXCLUDE;
1011             i = 0;
1012             while (opt) {
1013                 if (!(pua_cats = realloc(pua_cats, i + strlen(opt->strarg) + 3))) {
1014                     logg("!Can't allocate memory for pua_cats\n");
1015 
1016                     ret = 2;
1017                     goto done;
1018                 }
1019 
1020                 sprintf(pua_cats + i, ".%s", opt->strarg);
1021                 i += strlen(opt->strarg) + 1;
1022                 pua_cats[i] = 0;
1023 
1024                 opt = opt->nextarg;
1025             }
1026             pua_cats[i]     = '.';
1027             pua_cats[i + 1] = 0;
1028         }
1029 
1030         if ((opt = optget(opts, "include-pua"))->enabled) {
1031             if (pua_cats) {
1032                 logg("!--exclude-pua and --include-pua cannot be used at the same time\n");
1033 
1034                 free(pua_cats);
1035                 ret = 2;
1036                 goto done;
1037             }
1038 
1039             dboptions |= CL_DB_PUA_INCLUDE;
1040             i = 0;
1041             while (opt) {
1042                 if (!(pua_cats = realloc(pua_cats, i + strlen(opt->strarg) + 3))) {
1043                     logg("!Can't allocate memory for pua_cats\n");
1044                     ret = 2;
1045                     goto done;
1046                 }
1047 
1048                 sprintf(pua_cats + i, ".%s", opt->strarg);
1049                 i += strlen(opt->strarg) + 1;
1050                 pua_cats[i] = 0;
1051 
1052                 opt = opt->nextarg;
1053             }
1054 
1055             pua_cats[i]     = '.';
1056             pua_cats[i + 1] = 0;
1057         }
1058 
1059         if (pua_cats) {
1060             if ((ret = cl_engine_set_str(engine, CL_ENGINE_PUA_CATEGORIES, pua_cats))) {
1061                 logg("!cli_engine_set_str(CL_ENGINE_PUA_CATEGORIES) failed: %s\n", cl_strerror(ret));
1062 
1063                 free(pua_cats);
1064                 ret = 2;
1065                 goto done;
1066             }
1067 
1068             free(pua_cats);
1069         }
1070     }
1071 
1072     if (optget(opts, "dev-ac-only")->enabled)
1073         cl_engine_set_num(engine, CL_ENGINE_AC_ONLY, 1);
1074 
1075     if (optget(opts, "dev-ac-depth")->enabled)
1076         cl_engine_set_num(engine, CL_ENGINE_AC_MAXDEPTH, optget(opts, "dev-ac-depth")->numarg);
1077 
1078     if (optget(opts, "leave-temps")->enabled)
1079         cl_engine_set_num(engine, CL_ENGINE_KEEPTMP, 1);
1080 
1081     if (optget(opts, "force-to-disk")->enabled)
1082         cl_engine_set_num(engine, CL_ENGINE_FORCETODISK, 1);
1083 
1084     if (optget(opts, "bytecode-unsigned")->enabled)
1085         dboptions |= CL_DB_BYTECODE_UNSIGNED;
1086 
1087     if ((opt = optget(opts, "bytecode-timeout"))->enabled)
1088         cl_engine_set_num(engine, CL_ENGINE_BYTECODE_TIMEOUT, opt->numarg);
1089 
1090     if (optget(opts, "nocerts")->enabled)
1091         cl_engine_set_num(engine, CL_ENGINE_DISABLE_PE_CERTS, 1);
1092 
1093     if (optget(opts, "dumpcerts")->enabled)
1094         cl_engine_set_num(engine, CL_ENGINE_PE_DUMPCERTS, 1);
1095 
1096     if ((opt = optget(opts, "bytecode-mode"))->enabled) {
1097         enum bytecode_mode mode;
1098 
1099         if (!strcmp(opt->strarg, "ForceJIT"))
1100             mode = CL_BYTECODE_MODE_JIT;
1101         else if (!strcmp(opt->strarg, "ForceInterpreter"))
1102             mode = CL_BYTECODE_MODE_INTERPRETER;
1103         else if (!strcmp(opt->strarg, "Test"))
1104             mode = CL_BYTECODE_MODE_TEST;
1105         else
1106             mode = CL_BYTECODE_MODE_AUTO;
1107 
1108         cl_engine_set_num(engine, CL_ENGINE_BYTECODE_MODE, mode);
1109     }
1110 
1111     if ((opt = optget(opts, "statistics"))->enabled) {
1112         while (opt) {
1113             if (!strcasecmp(opt->strarg, "bytecode")) {
1114                 dboptions |= CL_DB_BYTECODE_STATS;
1115             } else if (!strcasecmp(opt->strarg, "pcre")) {
1116                 dboptions |= CL_DB_PCRE_STATS;
1117             }
1118             opt = opt->nextarg;
1119         }
1120     }
1121 
1122     /* JSON check to prevent engine loading if specified without libjson-c  */
1123 #if HAVE_JSON
1124     if (optget(opts, "gen-json")->enabled)
1125         options.general |= CL_SCAN_GENERAL_COLLECT_METADATA;
1126 #else
1127     if (optget(opts, "gen-json")->enabled) {
1128         logg("!Can't generate json (gen-json). libjson-c dev library was missing or misconfigured when ClamAV was built.\n");
1129 
1130         ret = 2;
1131         goto done;
1132     }
1133 #endif
1134 
1135     if ((opt = optget(opts, "tempdir"))->enabled) {
1136         if ((ret = cl_engine_set_str(engine, CL_ENGINE_TMPDIR, opt->strarg))) {
1137             logg("!cli_engine_set_str(CL_ENGINE_TMPDIR) failed: %s\n", cl_strerror(ret));
1138 
1139             ret = 2;
1140             goto done;
1141         }
1142     }
1143 
1144     if ((opt = optget(opts, "database"))->active) {
1145         while (opt) {
1146             if ((ret = cl_load(opt->strarg, engine, &info.sigs, dboptions))) {
1147                 logg("!%s\n", cl_strerror(ret));
1148 
1149                 ret = 2;
1150                 goto done;
1151             }
1152 
1153             opt = opt->nextarg;
1154         }
1155     } else {
1156         char *dbdir = freshdbdir();
1157 
1158         if ((ret = cl_load(dbdir, engine, &info.sigs, dboptions))) {
1159             logg("!%s\n", cl_strerror(ret));
1160 
1161             free(dbdir);
1162             ret = 2;
1163             goto done;
1164         }
1165 
1166         free(dbdir);
1167     }
1168 
1169     /* pcre engine limits - required for cl_engine_compile */
1170     if ((opt = optget(opts, "pcre-match-limit"))->active) {
1171         if ((ret = cl_engine_set_num(engine, CL_ENGINE_PCRE_MATCH_LIMIT, opt->numarg))) {
1172             logg("!cli_engine_set_num(CL_ENGINE_PCRE_MATCH_LIMIT) failed: %s\n", cl_strerror(ret));
1173             ret = 2;
1174             goto done;
1175         }
1176     }
1177 
1178     if ((opt = optget(opts, "pcre-recmatch-limit"))->active) {
1179         if ((ret = cl_engine_set_num(engine, CL_ENGINE_PCRE_RECMATCH_LIMIT, opt->numarg))) {
1180             logg("!cli_engine_set_num(CL_ENGINE_PCRE_RECMATCH_LIMIT) failed: %s\n", cl_strerror(ret));
1181             ret = 2;
1182             goto done;
1183         }
1184     }
1185 
1186     if ((ret = cl_engine_compile(engine)) != 0) {
1187         logg("!Database initialization error: %s\n", cl_strerror(ret));
1188         ret = 2;
1189         goto done;
1190     }
1191 
1192     if (isatty(fileno(stdout)) &&
1193         !optget(opts, "debug")->enabled &&
1194         !optget(opts, "quiet")->enabled &&
1195         !optget(opts, "infected")->enabled &&
1196         !optget(opts, "no-summary")->enabled) {
1197         /* For a space after the progress bars */
1198         logg("\n");
1199     }
1200 
1201     if (optget(opts, "archive-verbose")->enabled) {
1202         cl_engine_set_clcb_meta(engine, meta);
1203         cl_engine_set_clcb_pre_cache(engine, pre);
1204         cl_engine_set_clcb_post_scan(engine, post);
1205     }
1206 
1207     /* set limits */
1208 
1209     /* TODO: Remove deprecated option in a future feature release */
1210     if ((opt = optget(opts, "timelimit"))->active) {
1211         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANTIME, opt->numarg))) {
1212             logg("!cli_engine_set_num(CL_ENGINE_MAX_SCANTIME) failed: %s\n", cl_strerror(ret));
1213             ret = 2;
1214             goto done;
1215         }
1216     }
1217     if ((opt = optget(opts, "max-scantime"))->active) {
1218         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANTIME, opt->numarg))) {
1219             logg("!cli_engine_set_num(CL_ENGINE_MAX_SCANTIME) failed: %s\n", cl_strerror(ret));
1220             ret = 2;
1221             goto done;
1222         }
1223     }
1224 
1225     if ((opt = optget(opts, "max-scansize"))->active) {
1226         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANSIZE, opt->numarg))) {
1227             logg("!cli_engine_set_num(CL_ENGINE_MAX_SCANSIZE) failed: %s\n", cl_strerror(ret));
1228             ret = 2;
1229             goto done;
1230         }
1231     }
1232 
1233     if ((opt = optget(opts, "max-filesize"))->active) {
1234         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_FILESIZE, opt->numarg))) {
1235             logg("!cli_engine_set_num(CL_ENGINE_MAX_FILESIZE) failed: %s\n", cl_strerror(ret));
1236             ret = 2;
1237             goto done;
1238         }
1239     }
1240 
1241 #ifndef _WIN32
1242     if (getrlimit(RLIMIT_FSIZE, &rlim) == 0) {
1243         if (rlim.rlim_cur < (rlim_t)cl_engine_get_num(engine, CL_ENGINE_MAX_FILESIZE, NULL))
1244             logg("^System limit for file size is lower than engine->maxfilesize\n");
1245         if (rlim.rlim_cur < (rlim_t)cl_engine_get_num(engine, CL_ENGINE_MAX_SCANSIZE, NULL))
1246             logg("^System limit for file size is lower than engine->maxscansize\n");
1247     } else {
1248         logg("^Cannot obtain resource limits for file size\n");
1249     }
1250 #endif
1251 
1252     if ((opt = optget(opts, "max-files"))->active) {
1253         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_FILES, opt->numarg))) {
1254             logg("!cli_engine_set_num(CL_ENGINE_MAX_FILES) failed: %s\n", cl_strerror(ret));
1255             ret = 2;
1256             goto done;
1257         }
1258     }
1259 
1260     if ((opt = optget(opts, "max-recursion"))->active) {
1261         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_RECURSION, opt->numarg))) {
1262             logg("!cli_engine_set_num(CL_ENGINE_MAX_RECURSION) failed: %s\n", cl_strerror(ret));
1263             ret = 2;
1264             goto done;
1265         }
1266     }
1267 
1268     /* Engine max sizes */
1269 
1270     if ((opt = optget(opts, "max-embeddedpe"))->active) {
1271         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_EMBEDDEDPE, opt->numarg))) {
1272             logg("!cli_engine_set_num(CL_ENGINE_MAX_EMBEDDEDPE) failed: %s\n", cl_strerror(ret));
1273             ret = 2;
1274             goto done;
1275         }
1276     }
1277 
1278     if ((opt = optget(opts, "max-htmlnormalize"))->active) {
1279         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_HTMLNORMALIZE, opt->numarg))) {
1280             logg("!cli_engine_set_num(CL_ENGINE_MAX_HTMLNORMALIZE) failed: %s\n", cl_strerror(ret));
1281             ret = 2;
1282             goto done;
1283         }
1284     }
1285 
1286     if ((opt = optget(opts, "max-htmlnotags"))->active) {
1287         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_HTMLNOTAGS, opt->numarg))) {
1288             logg("!cli_engine_set_num(CL_ENGINE_MAX_HTMLNOTAGS) failed: %s\n", cl_strerror(ret));
1289             ret = 2;
1290             goto done;
1291         }
1292     }
1293 
1294     if ((opt = optget(opts, "max-scriptnormalize"))->active) {
1295         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCRIPTNORMALIZE, opt->numarg))) {
1296             logg("!cli_engine_set_num(CL_ENGINE_MAX_SCRIPTNORMALIZE) failed: %s\n", cl_strerror(ret));
1297             ret = 2;
1298             goto done;
1299         }
1300     }
1301 
1302     if ((opt = optget(opts, "max-ziptypercg"))->active) {
1303         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_ZIPTYPERCG, opt->numarg))) {
1304             logg("!cli_engine_set_num(CL_ENGINE_MAX_ZIPTYPERCG) failed: %s\n", cl_strerror(ret));
1305             ret = 2;
1306             goto done;
1307         }
1308     }
1309 
1310     if ((opt = optget(opts, "max-partitions"))->active) {
1311         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_PARTITIONS, opt->numarg))) {
1312             logg("!cli_engine_set_num(CL_ENGINE_MAX_PARTITIONS) failed: %s\n", cl_strerror(ret));
1313             ret = 2;
1314             goto done;
1315         }
1316     }
1317 
1318     if ((opt = optget(opts, "max-iconspe"))->active) {
1319         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_ICONSPE, opt->numarg))) {
1320             logg("!cli_engine_set_num(CL_ENGINE_MAX_ICONSPE) failed: %s\n", cl_strerror(ret));
1321             ret = 2;
1322             goto done;
1323         }
1324     }
1325 
1326     if ((opt = optget(opts, "max-rechwp3"))->active) {
1327         if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_RECHWP3, opt->numarg))) {
1328             logg("!cli_engine_set_num(CL_ENGINE_MAX_RECHWP3) failed: %s\n", cl_strerror(ret));
1329             ret = 2;
1330             goto done;
1331         }
1332     }
1333 
1334     if ((opt = optget(opts, "pcre-max-filesize"))->active) {
1335         if ((ret = cl_engine_set_num(engine, CL_ENGINE_PCRE_MAX_FILESIZE, opt->numarg))) {
1336             logg("!cli_engine_set_num(CL_ENGINE_PCRE_MAX_FILESIZE) failed: %s\n", cl_strerror(ret));
1337             ret = 2;
1338             goto done;
1339         }
1340     }
1341 
1342     /* set scan options */
1343     if (optget(opts, "allmatch")->enabled) {
1344         options.general |= CL_SCAN_GENERAL_ALLMATCHES;
1345     }
1346 
1347     /* TODO: Remove deprecated option in a future feature release */
1348     if ((optget(opts, "phishing-ssl")->enabled) ||
1349         (optget(opts, "alert-phishing-ssl")->enabled))
1350         options.heuristic |= CL_SCAN_HEURISTIC_PHISHING_SSL_MISMATCH;
1351 
1352     /* TODO: Remove deprecated option in a future feature release */
1353     if ((optget(opts, "phishing-cloak")->enabled) ||
1354         (optget(opts, "alert-phishing-cloak")->enabled))
1355         options.heuristic |= CL_SCAN_HEURISTIC_PHISHING_CLOAK;
1356 
1357     /* TODO: Remove deprecated option in a future feature release */
1358     if ((optget(opts, "partition-intersection")->enabled) ||
1359         (optget(opts, "alert-partition-intersection")->enabled))
1360         options.heuristic |= CL_SCAN_HEURISTIC_PARTITION_INTXN;
1361 
1362     if (optget(opts, "heuristic-scan-precedence")->enabled)
1363         options.general |= CL_SCAN_GENERAL_HEURISTIC_PRECEDENCE;
1364 
1365     if (optget(opts, "scan-archive")->enabled)
1366         options.parse |= CL_SCAN_PARSE_ARCHIVE;
1367 
1368     /* TODO: Remove deprecated option in a future feature release */
1369     if ((optget(opts, "detect-broken")->enabled) ||
1370         (optget(opts, "alert-broken")->enabled)) {
1371         options.heuristic |= CL_SCAN_HEURISTIC_BROKEN;
1372     }
1373 
1374     if (optget(opts, "alert-broken-media")->enabled) {
1375         options.heuristic |= CL_SCAN_HEURISTIC_BROKEN_MEDIA;
1376     }
1377 
1378     /* TODO: Remove deprecated option in a future feature release */
1379     if ((optget(opts, "block-encrypted")->enabled) ||
1380         (optget(opts, "alert-encrypted")->enabled)) {
1381         options.heuristic |= CL_SCAN_HEURISTIC_ENCRYPTED_ARCHIVE;
1382         options.heuristic |= CL_SCAN_HEURISTIC_ENCRYPTED_DOC;
1383     }
1384 
1385     if (optget(opts, "alert-encrypted-archive")->enabled)
1386         options.heuristic |= CL_SCAN_HEURISTIC_ENCRYPTED_ARCHIVE;
1387 
1388     if (optget(opts, "alert-encrypted-doc")->enabled)
1389         options.heuristic |= CL_SCAN_HEURISTIC_ENCRYPTED_DOC;
1390 
1391     /* TODO: Remove deprecated option in a future feature release */
1392     if ((optget(opts, "block-macros")->enabled) ||
1393         (optget(opts, "alert-macros")->enabled)) {
1394         options.heuristic |= CL_SCAN_HEURISTIC_MACROS;
1395     }
1396 
1397     if (optget(opts, "scan-pe")->enabled)
1398         options.parse |= CL_SCAN_PARSE_PE;
1399 
1400     if (optget(opts, "scan-elf")->enabled)
1401         options.parse |= CL_SCAN_PARSE_ELF;
1402 
1403     if (optget(opts, "scan-ole2")->enabled)
1404         options.parse |= CL_SCAN_PARSE_OLE2;
1405 
1406     if (optget(opts, "scan-pdf")->enabled)
1407         options.parse |= CL_SCAN_PARSE_PDF;
1408 
1409     if (optget(opts, "scan-swf")->enabled)
1410         options.parse |= CL_SCAN_PARSE_SWF;
1411 
1412     if (optget(opts, "scan-html")->enabled && optget(opts, "normalize")->enabled)
1413         options.parse |= CL_SCAN_PARSE_HTML;
1414 
1415     if (optget(opts, "scan-mail")->enabled)
1416         options.parse |= CL_SCAN_PARSE_MAIL;
1417 
1418     if (optget(opts, "scan-xmldocs")->enabled)
1419         options.parse |= CL_SCAN_PARSE_XMLDOCS;
1420 
1421     if (optget(opts, "scan-hwp3")->enabled)
1422         options.parse |= CL_SCAN_PARSE_HWP3;
1423 
1424     /* TODO: Remove deprecated option in a future feature release */
1425     if ((optget(opts, "algorithmic-detection")->enabled) && /* && used due to default-yes for both options */
1426         (optget(opts, "heuristic-alerts")->enabled)) {
1427         options.general |= CL_SCAN_GENERAL_HEURISTICS;
1428     }
1429 
1430     /* TODO: Remove deprecated option in a future feature release */
1431     if ((optget(opts, "block-max")->enabled) ||
1432         (optget(opts, "alert-exceeds-max")->enabled)) {
1433         options.heuristic |= CL_SCAN_HEURISTIC_EXCEEDS_MAX;
1434     }
1435 
1436 #ifdef HAVE__INTERNAL__SHA_COLLECT
1437     if (optget(opts, "dev-collect-hashes")->enabled)
1438         options.dev |= CL_SCAN_DEV_COLLECT_SHA;
1439 #endif
1440 
1441     if (optget(opts, "dev-performance")->enabled)
1442         options.dev |= CL_SCAN_DEV_COLLECT_PERFORMANCE_INFO;
1443 
1444     if (optget(opts, "detect-structured")->enabled) {
1445         options.heuristic |= CL_SCAN_HEURISTIC_STRUCTURED;
1446 
1447         if ((opt = optget(opts, "structured-ssn-format"))->enabled) {
1448             switch (opt->numarg) {
1449                 case 0:
1450                     options.heuristic |= CL_SCAN_HEURISTIC_STRUCTURED_SSN_NORMAL;
1451                     break;
1452                 case 1:
1453                     options.heuristic |= CL_SCAN_HEURISTIC_STRUCTURED_SSN_STRIPPED;
1454                     break;
1455                 case 2:
1456                     options.heuristic |= (CL_SCAN_HEURISTIC_STRUCTURED_SSN_NORMAL | CL_SCAN_HEURISTIC_STRUCTURED_SSN_STRIPPED);
1457                     break;
1458                 default:
1459                     logg("!Invalid argument for --structured-ssn-format\n");
1460                     ret = 2;
1461                     goto done;
1462             }
1463         } else {
1464             options.heuristic |= CL_SCAN_HEURISTIC_STRUCTURED_SSN_NORMAL;
1465         }
1466 
1467         if ((opt = optget(opts, "structured-ssn-count"))->active) {
1468             if ((ret = cl_engine_set_num(engine, CL_ENGINE_MIN_SSN_COUNT, opt->numarg))) {
1469                 logg("!cli_engine_set_num(CL_ENGINE_MIN_SSN_COUNT) failed: %s\n", cl_strerror(ret));
1470                 ret = 2;
1471                 goto done;
1472             }
1473         }
1474 
1475         if ((opt = optget(opts, "structured-cc-count"))->active) {
1476             if ((ret = cl_engine_set_num(engine, CL_ENGINE_MIN_CC_COUNT, opt->numarg))) {
1477                 logg("!cli_engine_set_num(CL_ENGINE_MIN_CC_COUNT) failed: %s\n", cl_strerror(ret));
1478                 ret = 2;
1479                 goto done;
1480             }
1481         }
1482 
1483         if ((opt = optget(opts, "structured-cc-mode"))->active) {
1484             switch (opt->numarg) {
1485                 case 0:
1486                     break;
1487                 case 1:
1488                     options.heuristic |= CL_SCAN_HEURISTIC_STRUCTURED_CC;
1489                     break;
1490                 default:
1491                     logg("!Invalid argument for --structured-cc-mode\n");
1492                     ret = 2;
1493                     goto done;
1494             }
1495         }
1496     } else {
1497         options.heuristic &= ~CL_SCAN_HEURISTIC_STRUCTURED;
1498     }
1499 
1500 #ifdef C_LINUX
1501     procdev = (dev_t)0;
1502     if (CLAMSTAT("/proc", &sb) != -1 && !sb.st_size)
1503         procdev = sb.st_dev;
1504 #endif
1505 
1506     /* check filetype */
1507     if (!opts->filename && !optget(opts, "file-list")->enabled) {
1508         /* we need full path for some reasons (eg. archive handling) */
1509         if (!getcwd(cwd, sizeof(cwd))) {
1510             logg("!Can't get absolute pathname of current working directory\n");
1511             ret = 2;
1512         } else {
1513             CLAMSTAT(cwd, &sb);
1514             scandirs(cwd, engine, opts, &options, 1, sb.st_dev);
1515         }
1516 
1517     } else if (opts->filename && !optget(opts, "file-list")->enabled && !strcmp(opts->filename[0], "-")) { /* read data from stdin */
1518         ret = scanstdin(engine, &options);
1519     } else {
1520         if (opts->filename && optget(opts, "file-list")->enabled)
1521             logg("^Only scanning files from --file-list (files passed at cmdline are ignored)\n");
1522 
1523         while ((filename = filelist(opts, &ret)) && (file = strdup(filename))) {
1524             if (LSTAT(file, &sb) == -1) {
1525                 perror(file);
1526                 logg("^%s: Can't access file\n", file);
1527                 ret = 2;
1528             } else {
1529                 for (i = strlen(file) - 1; i > 0; i--) {
1530                     if (file[i] == *PATHSEP)
1531                         file[i] = 0;
1532                     else
1533                         break;
1534                 }
1535 
1536                 if (S_ISLNK(sb.st_mode)) {
1537                     if (dirlnk == 0 && filelnk == 0) {
1538                         if (!printinfected)
1539                             logg("%s: Symbolic link\n", file);
1540                     } else if (CLAMSTAT(file, &sb) != -1) {
1541                         if (S_ISREG(sb.st_mode) && filelnk) {
1542                             scanfile(file, engine, opts, &options);
1543                         } else if (S_ISDIR(sb.st_mode) && dirlnk) {
1544                             scandirs(file, engine, opts, &options, 1, sb.st_dev);
1545                         } else {
1546                             if (!printinfected)
1547                                 logg("%s: Symbolic link\n", file);
1548                         }
1549                     }
1550                 } else if (S_ISREG(sb.st_mode)) {
1551                     scanfile(file, engine, opts, &options);
1552                 } else if (S_ISDIR(sb.st_mode)) {
1553                     scandirs(file, engine, opts, &options, 1, sb.st_dev);
1554                 } else {
1555                     logg("^%s: Not supported file type\n", file);
1556                     ret = 2;
1557                 }
1558             }
1559 
1560             free(file);
1561         }
1562     }
1563 
1564     if ((opt = optget(opts, "statistics"))->enabled) {
1565         while (opt) {
1566             if (!strcasecmp(opt->strarg, "bytecode")) {
1567                 cli_sigperf_print();
1568                 cli_sigperf_events_destroy();
1569             }
1570 #if HAVE_PCRE
1571             else if (!strcasecmp(opt->strarg, "pcre")) {
1572                 cli_pcre_perf_print();
1573                 cli_pcre_perf_events_destroy();
1574             }
1575 #endif
1576             opt = opt->nextarg;
1577         }
1578     }
1579 
1580 done:
1581     /* free the engine */
1582     cl_engine_free(engine);
1583 
1584     /* overwrite return code - infection takes priority */
1585     if (info.ifiles)
1586         ret = 1;
1587     else if (info.errors)
1588         ret = 2;
1589 
1590     return ret;
1591 }
1592