pull/1612/merge
Val S. 1 month ago committed by GitHub
commit cd4b957f54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 34
      examples/CMakeLists.txt
  2. 275
      examples/ex_file_inspection_callback.c
  3. 162
      examples/ex_prescan_callback.c
  4. 5
      examples/ex_scan_callbacks.c
  5. 26
      unit_tests/CMakeLists.txt
  6. 26
      unit_tests/examples/ex_cl_cvdunpack_test.py
  7. 33
      unit_tests/examples/ex_scan_callbacks_test.py

@ -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)

@ -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 <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <clamav.h>
#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;
}

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <clamav.h>
#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;
}

@ -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;
}

@ -196,6 +196,10 @@ if(WIN32)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/freshclam.exe FRESHCLAM)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/sigtool.exe SIGTOOL)
endif()
if(ENABLE_EXAMPLES)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/ex_scan_callbacks.exe EX_SCAN_CALLBACKS)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/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 $<TARGET_FILE:clamonacc>)
endif()
if(ENABLE_EXAMPLES)
set(EX_SCAN_CALLBACKS $<TARGET_FILE:ex_scan_callbacks>)
set(EX_CL_CVDUNPACK $<TARGET_FILE: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 $<TARGET_FILE:clamconf> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:freshclam-bin> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:sigtool> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
# Collect example programs, if built with ENABLE_EXAMPLES=ON
if ($<TARGET_EXISTS:ex_scan_callbacks>)
file(COPY $<TARGET_FILE:$<IF:$<TARGET_EXISTS:ex_scan_callbacks>,ex_scan_callbacks,check_clamav>> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
endif()
if ($<TARGET_EXISTS:ex_cl_cvdunpack>)
file(COPY $<TARGET_FILE:$<IF:$<TARGET_EXISTS:ex_cl_cvdunpack>,ex_cl_cvdunpack,check_clamav>> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
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 ($<TARGET_EXISTS:ClamAV::libunrar_iface>)
file(COPY $<TARGET_FILE:$<IF:$<TARGET_EXISTS:ClamAV::libunrar_iface>,ClamAV::libunrar_iface,check_clamav>> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
endif()
# Collect example programs, if built with ENABLE_EXAMPLES=ON
if ($<TARGET_EXISTS:ex_scan_callbacks>)
file(COPY $<TARGET_FILE:$<IF:$<TARGET_EXISTS:ex_scan_callbacks>,ex_scan_callbacks,check_clamav>> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
endif()
if ($<TARGET_EXISTS:ex_cl_cvdunpack>)
file(COPY $<TARGET_FILE:$<IF:$<TARGET_EXISTS:ex_cl_cvdunpack>,ex_cl_cvdunpack,check_clamav>> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
endif()
]])
endif()

@ -8,6 +8,8 @@ import os
import platform
import shutil
import sys
from pathlib import Path
sys.path.append('../unit_tests')
import testcase
@ -24,27 +26,9 @@ class TC(testcase.TestCase):
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(

@ -41,6 +41,7 @@ import os
import platform
import shutil
import sys
from pathlib import Path
sys.path.append('../unit_tests')
import testcase
@ -58,27 +59,9 @@ class TC(testcase.TestCase):
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 @@ class TC(testcase.TestCase):
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 @@ class TC(testcase.TestCase):
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 @@ class TC(testcase.TestCase):
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 @@ class TC(testcase.TestCase):
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'
]

Loading…
Cancel
Save