diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 10226a1734..1400347ad0 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -39,40 +39,6 @@ if(ENABLE_STATIC_LIB) endif() endif() -add_executable(ex_prescan_callback) -target_sources(ex_prescan_callback - PRIVATE ex_prescan_callback.c) -set_target_properties(ex_prescan_callback PROPERTIES COMPILE_FLAGS "${WARNCFLAGS}") -target_link_libraries(ex_prescan_callback - PRIVATE - ClamAV::libclamav) -if(LLVM_FOUND) - target_link_directories( ex_prescan_callback PUBLIC ${LLVM_LIBRARY_DIRS} ) - target_link_libraries( ex_prescan_callback PUBLIC ${LLVM_LIBRARIES} ) -endif() -if(WIN32) - install(TARGETS ex_prescan_callback DESTINATION .) -else() - install(TARGETS ex_prescan_callback DESTINATION ${CMAKE_INSTALL_BINDIR}) -endif() - -add_executable(ex_file_inspection_callback) -target_sources(ex_file_inspection_callback - PRIVATE ex_file_inspection_callback.c) -set_target_properties(ex_file_inspection_callback PROPERTIES COMPILE_FLAGS "${WARNCFLAGS}") -target_link_libraries(ex_file_inspection_callback - PRIVATE - ClamAV::libclamav) -if(LLVM_FOUND) - target_link_directories( ex_file_inspection_callback PUBLIC ${LLVM_LIBRARY_DIRS} ) - target_link_libraries( ex_file_inspection_callback PUBLIC ${LLVM_LIBRARIES} ) -endif() -if(WIN32) - install(TARGETS ex_file_inspection_callback DESTINATION .) -else() - install(TARGETS ex_file_inspection_callback DESTINATION ${CMAKE_INSTALL_BINDIR}) -endif() - add_executable(ex_scan_callbacks) target_sources(ex_scan_callbacks PRIVATE ex_scan_callbacks.c) diff --git a/examples/ex_file_inspection_callback.c b/examples/ex_file_inspection_callback.c deleted file mode 100644 index 9609da4cb4..0000000000 --- a/examples/ex_file_inspection_callback.c +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2020-2025 Cisco Systems, Inc. and/or its affiliates. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - */ - -/* - * This example demonstrates using callbacks to record information about each - * file found during a recursive scan. - */ - -#include -#include -#include -#include -#ifndef _WIN32 -#include -#endif -#include -#include -#include - -#include - -#ifndef MIN -#define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#endif -#ifndef MAX -#define MAX(a, b) (((a) > (b)) ? (a) : (b)) -#endif - -/** Max # of bytes to show for archive inspection preview */ -#define MAX_PREVIEW 10 - -cl_error_t inspection_callback( - int fd, - const char *type, - const char **ancestors, // array of null-terminated strings, array size == recursion_level - size_t parent_file_size, - const char *file_name, - size_t file_size, - const char *file_buffer, - uint32_t recursion_level, - uint32_t attributes, // E.g.: was normalized, decrypted, etc. See LAYER_ATTRIBUTES_* flags in clamav.h - void *context) // Could be used to retrieve / store contextual information for app -{ - size_t i = 0; - - // printf("Message: %s\n", (char *)context.blah); - UNUSEDPARAM(context); - - printf("ancestors: "); - for (i = 0; i < recursion_level; i++) { - printf("%s", ancestors[i]); - if (i + 1 < recursion_level) { - printf(" > "); - } - } - printf("\n"); - printf("parent size: %zu\n", parent_file_size); - printf("file name: %s\n", file_name); - printf("file desc: %d\n", fd); - printf("file size: %zu\n", file_size); - printf("file type: %s\n", type); - printf("recursion level: %u\n", recursion_level); - printf("decrypted: %s\n", attributes & LAYER_ATTRIBUTES_DECRYPTED ? "yes" : "no"); - printf("normalized: %s\n", attributes & LAYER_ATTRIBUTES_NORMALIZED ? "yes" : "no"); - printf("file preview: "); - for (i = 0; i < MIN(file_size, MAX_PREVIEW); i++) { - uint8_t byte = file_buffer[i]; - printf("%02x ", byte); - } - printf("\n\n"); - - return CL_CLEAN; /* keep scanning */ -} - -cl_error_t post_callback( - int fd, - int result, - const char *virname, - void *context) // Could be used to retrieve / store contextual information for app -{ - (void)fd; - (void)context; - - printf("result: %d\n", result); - printf("virname: %s\n", virname); - printf("\n\n"); - - return CL_CLEAN; // respect the original result -} - -/* - * Exit codes: - * 0: clean - * 1: infected - * 2: error - */ - -int main(int argc, char **argv) -{ - int status = 2; - cl_error_t ret = CL_ERROR; - - int db_fd = -1; - int target_fd = -1; - - unsigned long int size = 0; - long double mb; - const char *virname; - const char *filename; - struct cl_engine *engine = NULL; - struct cl_scan_options options; - char database_filepath[256]; - bool created_database = false; - - STATBUF st; - char *mem = NULL; - ssize_t bytes_read; - cl_fmap_t *map = NULL; - - if (argc != 2) { - printf("Usage: %s file\n", argv[0]); - return 2; - } - - filename = argv[1]; - - if ((target_fd = open(argv[1], O_RDONLY)) == -1) { - printf("Can't open file %s\n", argv[1]); - goto done; - } - - if (FSTAT(target_fd, &st)) { - printf("fmap: fstat failed\n"); - goto done; - } - - if (NULL == (mem = malloc((size_t)st.st_size))) { - printf("malloc failed, buffer size: %zu\n", (size_t)st.st_size); - goto done; - } - - bytes_read = read(target_fd, mem, (size_t)st.st_size); - if (bytes_read != (ssize_t)st.st_size) { - printf("read failed, buffer size: %zu\n", (size_t)st.st_size); - goto done; - } - - map = cl_fmap_open_memory(mem, (size_t)st.st_size); - - if (CL_SUCCESS != (ret = cl_init(CL_INIT_DEFAULT))) { - printf("Can't initialize libclamav: %s\n", cl_strerror(ret)); - goto done; - } - - if (!(engine = cl_engine_new())) { - printf("Can't create new engine\n"); - goto done; - } - - /* Example version macro usage to determine if new feature is available */ -#if defined(LIBCLAMAV_VERSION_NUM) && (LIBCLAMAV_VERSION_NUM >= 0x090400) - /* Example feature usage lowering max scan time to 15 seconds. */ - cl_engine_set_num(engine, CL_ENGINE_MAX_SCANTIME, 15000); -#endif - cl_engine_set_num(engine, CL_ENGINE_MAX_SCANSIZE, 1024 /*MB*/ * 1024 /*KB*/ * 1024 /*bytes*/); - cl_engine_set_num(engine, CL_ENGINE_MAX_FILESIZE, 1024 /*MB*/ * 1024 /*KB*/ * 1024 /*bytes*/); - - /* load a pwdb signature, to demonstrate the "was decrypted" feature */ -#define PWDB_FILENAME "./ex3.pwdb" - unsigned int signo = 0; - if (-1 == (db_fd = open(PWDB_FILENAME, O_CREAT | O_RDWR, 0600))) { - printf("Failed to create ex3.pwdb database\n"); - goto done; - } - -#define PWDB_SIGNATURE "SignatureName;Engine:80-1000;0;virus" - if (-1 == write(db_fd, PWDB_SIGNATURE, strlen(PWDB_SIGNATURE))) { - printf("Failed write to ex3.pwdb database\n"); - goto done; - } - - if (CL_SUCCESS != (ret = cl_load(PWDB_FILENAME, engine, &signo, CL_DB_STDOPT))) { - printf("Database load error: %s\n", cl_strerror(ret)); - goto done; - } - - close(db_fd); - - /* build engine */ - if (CL_SUCCESS != (ret = cl_engine_compile(engine))) { - printf("Database initialization error: %s\n", cl_strerror(ret)); - goto done; - } - - /* scan file descriptor */ - memset(&options, 0, sizeof(struct cl_scan_options)); - options.parse |= ~0; /* enable all parsers */ - options.general |= CL_SCAN_GENERAL_HEURISTICS; /* enable heuristic alert options */ - options.general |= CL_SCAN_GENERAL_ALLMATCHES; /* run in all-match mode, so it keeps looking for alerts after the first one */ - - options.heuristic |= CL_SCAN_HEURISTIC_ENCRYPTED_ARCHIVE; - options.heuristic |= CL_SCAN_HEURISTIC_ENCRYPTED_DOC; - - /* - * Set our callbacks for inspecting embedded files during the scan. - */ - cl_engine_set_clcb_file_inspection(engine, &inspection_callback); - /* - * Set our callbacks for inspecting embedded files during the scan. - */ - cl_engine_set_clcb_post_scan(engine, &post_callback); - - printf("Testing file inspection on FD %d - %s\n", target_fd, filename); - - if (CL_VIRUS == (ret = cl_scanmap_callback( - map, - filename, - &virname, - &size, - engine, - &options, - (void *)"Hello, World!"))) { - printf("Virus detected: %s\n", virname); - } else { - if (ret != CL_CLEAN) { - printf("Error: %s\n", cl_strerror(ret)); - goto done; - } - } - /* calculate size of scanned data */ - mb = size * (CL_COUNT_PRECISION / 1024) / 1024.0; - printf("Data scanned: %2.2Lf MB\n", mb); - - status = ret == CL_VIRUS ? 1 : 0; - -done: - - if (NULL != map) { - cl_fmap_close(map); - } - if (NULL != mem) { - free(mem); - } - unlink(PWDB_FILENAME); - if (-1 != db_fd) { - close(db_fd); - } - if (-1 != target_fd) { - close(target_fd); - } - if (NULL != engine) { - cl_engine_free(engine); - } - if (true == created_database) { - unlink(database_filepath); - } - - return status; -} diff --git a/examples/ex_prescan_callback.c b/examples/ex_prescan_callback.c deleted file mode 100644 index 7888ed9a89..0000000000 --- a/examples/ex_prescan_callback.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2020-2025 Cisco Systems, Inc. and/or its affiliates. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - */ - -/* - * This example demonstrates using callbacks to record information about each - * file found during a recursive scan. - */ - -#include -#include -#include -#ifndef _WIN32 -#include -#endif -#include -#include -#include - -#include - -#ifdef _WIN32 -typedef int ssize_t; -#endif - -cl_error_t scan_callback(int fd, const char *type, void *context) -{ - char buf[10]; - ssize_t bytes_read = 0; - (void)context; /* Could be used to retrieve/store info */ - - printf("fd: %u, type: %s, initial bytes: ", fd, type); - if (-1 != (bytes_read = read(fd, &buf, sizeof(buf)))) { - /* Was able to read a few bytes */ - ssize_t i = 0; - for (i = 0; i < bytes_read; i++) { - printf("%02x", (unsigned int)buf[i]); - } - printf("\n"); - } - return CL_CLEAN; /* keep scanning */ -} - -/* - * Exit codes: - * 0: clean - * 1: infected - * 2: error - */ - -int main(int argc, char **argv) -{ - int status = 2; - cl_error_t ret = CL_ERROR; - - char *db_filepath = NULL; - int db_fd = -1; - int target_fd = -1; - - unsigned long int size = 0; - long double mb; - const char *virname; - const char *filename; - struct cl_engine *engine = NULL; - struct cl_scan_options options; - - if (argc != 2) { - printf("Usage: %s file\n", argv[0]); - return 2; - } - - filename = argv[1]; - - if ((target_fd = open(argv[1], O_RDONLY)) == -1) { - printf("Can't open file %s\n", argv[1]); - goto done; - } - - if (CL_SUCCESS != (ret = cl_init(CL_INIT_DEFAULT))) { - printf("Can't initialize libclamav: %s\n", cl_strerror(ret)); - goto done; - } - - if (!(engine = cl_engine_new())) { - printf("Can't create new engine\n"); - goto done; - } - - /* Example version macro usage to determine if new feature is available */ -#if defined(LIBCLAMAV_VERSION_NUM) && (LIBCLAMAV_VERSION_NUM >= 0x090400) - /* Example feature usage lowering max scan time to 15 seconds. */ - cl_engine_set_num(engine, CL_ENGINE_MAX_SCANTIME, 15000); -#endif - cl_engine_set_num(engine, CL_ENGINE_MAX_SCANSIZE, 1024 /*MB*/ * 1024 /*KB*/ * 1024 /*bytes*/); - cl_engine_set_num(engine, CL_ENGINE_MAX_FILESIZE, 1024 /*MB*/ * 1024 /*KB*/ * 1024 /*bytes*/); - - /* build engine */ - if (CL_SUCCESS != (ret = cl_engine_compile(engine))) { - printf("Database initialization error: %s\n", cl_strerror(ret)); - goto done; - } - - /* scan file descriptor */ - memset(&options, 0, sizeof(struct cl_scan_options)); - options.parse |= ~0; /* enable all parsers */ - options.general |= CL_SCAN_GENERAL_HEURISTICS; /* enable heuristic alert options */ - options.general |= CL_SCAN_GENERAL_ALLMATCHES; /* run in all-match mode, so it keeps looking for alerts after the first one */ - options.general |= CL_SCAN_GENERAL_COLLECT_METADATA; /* collect metadata may enable collecting additional filenames (like in zip) */ - - /* - * Set our callbacks for inspecting embedded files during the scan. - */ - cl_engine_set_clcb_pre_scan(engine, &scan_callback); - - printf("Testing prescan on FD %d - %s\n", target_fd, filename); - - if (CL_VIRUS == (ret = cl_scandesc(target_fd, filename, &virname, &size, engine, &options))) { - printf("Virus detected: %s\n", virname); - } else { - if (ret != CL_CLEAN) { - printf("Error: %s\n", cl_strerror(ret)); - goto done; - } - } - /* calculate size of scanned data */ - mb = size * (CL_COUNT_PRECISION / 1024) / 1024.0; - printf("Data scanned: %2.2Lf MB\n", mb); - - status = ret == CL_VIRUS ? 1 : 0; - -done: - - if (-1 != db_fd) { - close(db_fd); - } - if (-1 != target_fd) { - close(target_fd); - } - if (NULL != engine) { - cl_engine_free(engine); - } - if (NULL != db_filepath) { - free(db_filepath); - } - - return status; -} diff --git a/examples/ex_scan_callbacks.c b/examples/ex_scan_callbacks.c index df4b27957c..3f606b3a09 100644 --- a/examples/ex_scan_callbacks.c +++ b/examples/ex_scan_callbacks.c @@ -375,8 +375,9 @@ script_context_t *read_script_commands(const char *script_filepath) } size_t bytes_read = fread(script_contents, 1, script_size, script_file); - if (bytes_read != (size_t)script_size) { - printf("Error reading script file %s\n", script_filepath); + if (bytes_read != (size_t)script_size && ferror(script_file) != 0) { + printf("Error reading script file %s. Bytes read: %zu, Script size: %zu\n", + script_filepath, bytes_read, (size_t)script_size); status = 2; goto done; } diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt index 237a2bb529..8b3e5c7fe1 100644 --- a/unit_tests/CMakeLists.txt +++ b/unit_tests/CMakeLists.txt @@ -196,6 +196,10 @@ if(WIN32) file(TO_NATIVE_PATH $/freshclam.exe FRESHCLAM) file(TO_NATIVE_PATH $/sigtool.exe SIGTOOL) endif() + if(ENABLE_EXAMPLES) + file(TO_NATIVE_PATH $/ex_scan_callbacks.exe EX_SCAN_CALLBACKS) + file(TO_NATIVE_PATH $/ex_cl_cvdunpack.exe EX_CL_CVDUNPACK) + endif() # Convert the CVD_CERTS_DIR to a native path for Windows (replacing forward slashes with backslashes). file(TO_NATIVE_PATH ${CVD_CERTS_DIR} CVD_CERTS_DIR) @@ -259,6 +263,10 @@ else() if(ENABLE_CLAMONACC) set(CLAMONACC $) endif() + if(ENABLE_EXAMPLES) + set(EX_SCAN_CALLBACKS $) + set(EX_CL_CVDUNPACK $) + endif() endif() endif() @@ -302,6 +310,8 @@ set(ENVIRONMENT SIGTOOL=${SIGTOOL} CLAMAV_MILTER=${CLAMAV_MILTER} CLAMONACC=${CLAMONACC} + EX_SCAN_CALLBACKS=${EX_SCAN_CALLBACKS} + EX_CL_CVDUNPACK=${EX_CL_CVDUNPACK} ) # The Rust openssl-sys crate needs to know how to find the OpenSSL headers and libraries. @@ -518,6 +528,14 @@ if(WIN32) file(COPY $ DESTINATION $) file(COPY $ DESTINATION $) file(COPY $ DESTINATION $) + + # Collect example programs, if built with ENABLE_EXAMPLES=ON + if ($) + file(COPY $,ex_scan_callbacks,check_clamav>> DESTINATION $) + endif() + if ($) + file(COPY $,ex_cl_cvdunpack,check_clamav>> DESTINATION $) + endif() ]]) else() # We don't have libfreshclam unit tests, so no need to check if ENABLE_LIBCLAMAV_ONLY is enabled. @@ -561,6 +579,14 @@ if(WIN32) if ($) file(COPY $,ClamAV::libunrar_iface,check_clamav>> DESTINATION $) endif() + + # Collect example programs, if built with ENABLE_EXAMPLES=ON + if ($) + file(COPY $,ex_scan_callbacks,check_clamav>> DESTINATION $) + endif() + if ($) + file(COPY $,ex_cl_cvdunpack,check_clamav>> DESTINATION $) + endif() ]]) endif() diff --git a/unit_tests/examples/ex_cl_cvdunpack_test.py b/unit_tests/examples/ex_cl_cvdunpack_test.py index cf23d61a2b..cd6b2c4d10 100644 --- a/unit_tests/examples/ex_cl_cvdunpack_test.py +++ b/unit_tests/examples/ex_cl_cvdunpack_test.py @@ -8,6 +8,8 @@ import platform import shutil import sys +from pathlib import Path + sys.path.append('../unit_tests') import testcase @@ -24,27 +26,9 @@ def setUpClass(cls): super(TC, cls).setUpClass() # Find the example program - if operating_system == 'windows': - # Windows needs the example program to be in the same directory as libclamav and the rest. - shutil.copy( - str(TC.path_build / 'examples' / program_name + '.exe'), - str(TC.path_build / 'unit_tests' / program_name + '.exe'), - ) - - TC.example_program = TC.path_build / 'unit_tests' / program_name + '.exe' - if not TC.example_program.exists(): - # Try the static version. - TC.example_program = TC.path_build / 'unit_tests' / program_name + '_static.exe' - if not TC.example_program.exists(): - raise Exception('Could not find the example program.') - else: - # Linux and macOS can use the LD_LIBRARY_PATH environment variable to find libclamav - TC.example_program = TC.path_build / 'examples' / program_name - if not TC.example_program.exists(): - # Try the static version. - TC.example_program = TC.path_build / 'examples' / program_name + '_static' - if not TC.example_program.exists(): - raise Exception('Could not find the example program.') + TC.example_program = Path(os.getenv("EX_CL_CVDUNPACK")) + if not TC.example_program.exists(): + raise Exception(f'Could not find the example program {TC.example_program}') # Copy the test cvd to the temp directory shutil.copyfile( diff --git a/unit_tests/examples/ex_scan_callbacks_test.py b/unit_tests/examples/ex_scan_callbacks_test.py index 03b69aab43..875d4dc2b2 100644 --- a/unit_tests/examples/ex_scan_callbacks_test.py +++ b/unit_tests/examples/ex_scan_callbacks_test.py @@ -41,6 +41,7 @@ import platform import shutil import sys +from pathlib import Path sys.path.append('../unit_tests') import testcase @@ -58,27 +59,9 @@ def setUpClass(cls): super(TC, cls).setUpClass() # Find the example program - if operating_system == 'windows': - # Windows needs the example program to be in the same directory as libclamav and the rest. - shutil.copy( - str(TC.path_build / 'examples' / program_name + '.exe'), - str(TC.path_build / 'unit_tests' / program_name + '.exe'), - ) - - TC.example_program = TC.path_build / 'unit_tests' / program_name + '.exe' - if not TC.example_program.exists(): - # Try the static version. - TC.example_program = TC.path_build / 'unit_tests' / program_name + '_static.exe' - if not TC.example_program.exists(): - raise Exception('Could not find the example program.') - else: - # Linux and macOS can use the LD_LIBRARY_PATH environment variable to find libclamav - TC.example_program = TC.path_build / 'examples' / program_name - if not TC.example_program.exists(): - # Try the static version. - TC.example_program = TC.path_build / 'examples' / program_name + '_static' - if not TC.example_program.exists(): - raise Exception('Could not find the example program.') + TC.example_program = Path(os.getenv("EX_SCAN_CALLBACKS")) + if not TC.example_program.exists(): + raise Exception(f'Could not find the example program {TC.example_program}') @classmethod def tearDownClass(cls): @@ -168,8 +151,8 @@ def test_cl_scan_callbacks_clam_zip_basic(self): f.write('2\n') # Return CL_SUCCESS to keep scanning expected_results += [ - 'Recursion Level: 0', 'In POST_SCAN callback', + 'Recursion Level: 0', 'File Name: clam.zip', 'File Type: CL_TYPE_ZIP' ] @@ -287,8 +270,8 @@ def test_cl_scan_callbacks_clam_zip_basic_one_match(self): f.write('2\n') # Return CL_SUCCESS to keep scanning expected_results += [ - 'Recursion Level: 0', 'In POST_SCAN callback', + 'Recursion Level: 0', 'File Name: clam.zip', 'File Type: CL_TYPE_ZIP' ] @@ -401,8 +384,8 @@ def test_cl_scan_callbacks_clam_zip_ignore_alert(self): f.write('2\n') # Return CL_SUCCESS to keep scanning expected_results += [ - 'Recursion Level: 0', 'In POST_SCAN callback', + 'Recursion Level: 0', 'File Name: clam.zip', 'File Type: CL_TYPE_ZIP', ] @@ -643,8 +626,8 @@ def test_cl_scan_callbacks_clam_verify(self): f.write('3\n') # Return CL_VIRUS to keep scanning and accept the alert expected_results += [ - 'Recursion Level: 0', 'In POST_SCAN callback', + 'Recursion Level: 0', 'File Name: clam.zip', 'File Type: CL_TYPE_ZIP' ]