CMake: Add CTest support to match Autotools checks

An ENABLE_TESTS CMake option is provided so that users can disable
testing if they don't want it. Instructions for how to use this
included in the INSTALL.cmake.md file.

If you run `ctest`, each testcase will write out a log file to the
<build>/unit_tests directory.

As with Autotools' make check, the test files are from test/.split
and unit_tests/.split files, but for CMake these are generated at
build time instead of at test time.

On Posix systems, sets the LD_LIBRARY_PATH so that ClamAV-compiled
libraries can be loaded when running tests.

On Windows systems, CTest will identify and collect all library
dependencies and assemble a temporarily install under the
build/unit_tests directory so that the libraries can be loaded when
running tests.

The same feature is used on Windows when using CMake to install to
collect all DLL dependencies so that users don't have to install them
manually afterwards.

Each of the CTest tests are run using a custom wrapper around Python's
unittest framework, which is also responsible for finding and inserting
valgrind into the valgrind tests on Posix systems.

Unlike with Autotools, the CMake CTest Valgrind-tests are enabled by
default, if Valgrind can be found. There's no need to set VG=1.
CTest's memcheck module is NOT supported, because we use Python to
orchestrate our tests.

Added a bunch of Windows compatibility changes to the unit tests.
These were primarily changing / to PATHSEP and making adjustments
to use Win32 C headers and ifdef out the POSIX ones which aren't
available on Windows. Also disabled a bunch of tests on Win32
that don't work on Windows, notably the mmap ones and FD-passing
(i.e. FILEDES) ones.

Add JSON_C_HAVE_INTTYPES_H definition to clamav-config.h to eliminate
warnings on Windows where json.h is included after inttypes.h because
json-c's inttypes replacement relies on it.
This is a it of a hack and may be removed if json-c fixes their
inttypes header stuff in the future.

Add preprocessor definitions on Windows to disable MSVC warnings about
CRT secure and nonstandard functions. While there may be a better
solution, this is needed to be able to see other more serious warnings.

Add missing file comment block and copyright statement for clamsubmit.c.
Also change json-c/json.h include filename to json.h in clamsubmit.c.
The directory name is not required.

Changed the hash table data integer type from long, which is poorly
defined, to size_t -- which is capable of storing a pointer. Fixed a
bunch of casts regarding this variable to eliminate warnings.

Fixed two bugs causing utf8 encoding unit tests to fail on Windows:
- The in_size variable should be the number of bytes, not the character
  count. This was was causing the SHIFT_JIS (japanese codepage) to UTF8
  transcoding test to only transcode half the bytes.
- It turns out that the MultiByteToWideChar() API can't transcode
  UTF16-BE to UTF16-LE. The solution is to just iterate over the buffer
  and flip the bytes on each uint16_t. This but was causing the UTF16-BE
  to UTF8 tests to fail.

I also split up the utf8 transcoding tests into separate tests so I
could see all of the failures instead of just the first one.

Added a flags parameter to the unit test function to open testfiles
because it turns out that on Windows if a file contains the \r\n it will
replace it with just \n if you opened the file as a text file instead of
as binary. However, if we open the CBC files as binary, then a bunch of
bytecode tests fail. So I've changed the tests to open the CBC files in
the bytecode tests as text files and open all other files as binary.

Ported the feature tests from shell scripts to Python using a modified
version of our QA test-framework, which is largely compatible and will
allow us to migrate some QA tests into this repo. I'd like to add GitHub
Actions pipelines in the future so that all public PR's get some testing
before anyone has to manually review them.

The clamd --log option was missing from the help string, though it
definitely works. I've added it in this commit.
It appears that clamd.c was never clang-format'd, so this commit also
reformats clamd.c.

Some of the check_clamd tests expected the path returned by clamd to
match character for character with original path sent to clamd. However,
as we now evaluate real paths before a scan, the path returned by clamd
isn't going to match the relative (and possibly symlink-ridden) path
passed to clamdscan. I fixed this test by changing the test to search
for the basename: <signature> FOUND within the response instead of
matching the exact path.

Autotools: Link check_clamd with libclamav so we can use our utility
functions in check_clamd.c.
pull/152/head^2
Micah Snyder 5 years ago
parent 978fea6788
commit 2552cfd0d1
  1. 5
      .gitignore
  2. 53
      CMakeLists.txt
  3. 4
      CMakeOptions.cmake
  4. 31
      INSTALL.cmake.md
  5. 5
      clamav-config.h.cmake.in
  6. 2
      clamav-milter/CMakeLists.txt
  7. 5
      clambc/CMakeLists.txt
  8. 5
      clamconf/CMakeLists.txt
  9. 5
      clamd/CMakeLists.txt
  10. 91
      clamd/clamd.c
  11. 5
      clamdscan/CMakeLists.txt
  12. 28
      clamdtop/CMakeLists.txt
  13. 5
      clamonacc/CMakeLists.txt
  14. 5
      clamscan/CMakeLists.txt
  15. 32
      clamsubmit/CMakeLists.txt
  16. 28
      clamsubmit/clamsubmit.c
  17. 124
      cmake/FindLibcheck.cmake
  18. 36
      cmake/FindValgrind.cmake
  19. 2
      examples/CMakeLists.txt
  20. 5
      freshclam/CMakeLists.txt
  21. 103
      libclamav/CMakeLists.txt
  22. 6
      libclamav/bytecode.c
  23. 113
      libclamav/entconv.c
  24. 45
      libclamav/hashtab.c
  25. 4
      libclamav/hashtab.h
  26. 20
      libclamav/jsparse/js-norm.c
  27. 1
      libclamav/libclamav.map
  28. 66
      libclamav/others.c
  29. 4
      libclamav/regex_list.c
  30. 5
      libclamunrar/CMakeLists.txt
  31. 13
      libclamunrar_iface/CMakeLists.txt
  32. 30
      libfreshclam/CMakeLists.txt
  33. 5
      shared/CMakeLists.txt
  34. 5
      sigtool/CMakeLists.txt
  35. 76
      test/CMakeLists.txt
  36. 47
      test/assemble_testfile.py
  37. 385
      unit_tests/CMakeLists.txt
  38. 6
      unit_tests/Makefile.am
  39. 1
      unit_tests/Run-GetLibs.ctest
  40. 104
      unit_tests/check_bytecode.c
  41. 301
      unit_tests/check_clamav.c
  42. 219
      unit_tests/check_clamd.c
  43. 12
      unit_tests/check_common.sh
  44. 14
      unit_tests/check_disasm.c
  45. 58
      unit_tests/check_htmlnorm.c
  46. 11
      unit_tests/check_regex.c
  47. 3
      unit_tests/checks.h
  48. 4
      unit_tests/checks_common.h
  49. 455
      unit_tests/clamd_test.py
  50. 220
      unit_tests/clamscan_test.py
  51. 89
      unit_tests/freshclam_test.py
  52. 50
      unit_tests/libclamav_test.py
  53. 76
      unit_tests/sigtool_test.py
  54. 918
      unit_tests/testcase.py
  55. 5
      win32/compat/CMakeLists.txt

5
.gitignore vendored

@ -10,6 +10,11 @@ install_manifest.txt
compile_commands.json
CTestTestfile.cmake
# Python
__pycache__/
*.py[cod]
*$py.class
# Ninja
.ninja_deps
.ninja_log

@ -1,10 +1,11 @@
# Copyright (C) 2019-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
cmake_minimum_required( VERSION 3.14 )
set(CMAKE_C_STANDARD 90)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
cmake_policy(SET CMP0087 NEW) # support generator expressions in install(CODE) and install(SCRIPT)
# Change this on a release:
# During active development: set(VERSION_SUFFIX "-devel-${TODAY}")
@ -82,6 +83,7 @@ set(ENABLE_APP_DEFAULT ON)
set(ENABLE_MILTER_DEFAULT OFF)
set(ENABLE_CLAMONACC_DEFAULT ON)
set(ENABLE_EXAMPLES_DEFAULT OFF)
set(ENABLE_TESTS_DEFAULT ON)
if(WIN32)
set(ENABLE_DOCS_DEFAULT OFF)
else()
@ -137,8 +139,24 @@ endif()
# Use the `lib` prefix on Windows, to match previous ClamAV build
#
if(WIN32)
set(CMAKE_SHARED_LIBRARY_PREFIX "lib")
set(CMAKE_STATIC_LIBRARY_PREFIX "lib")
set(CMAKE_SHARED_LIBRARY_PREFIX "lib")
set(CMAKE_STATIC_LIBRARY_PREFIX "lib")
endif()
#
# Find Test dependencies
#
if(ENABLE_TESTS)
# Used for the libclamav unit test framework
find_package(Libcheck REQUIRED)
# Used to generate the test files and for the application feature test framework
find_package(Python3 REQUIRED)
# Check for valgrind. If it exists, we'll enable extra tests that use valgrind.
if(LINUX)
find_package(Valgrind)
endif()
endif()
#
@ -211,7 +229,7 @@ if(BYTECODE_RUNTIME STREQUAL "llvm")
endif()
endif()
# libfreshclam & app dependencies
# libfreshclam & application dependencies
if(NOT ENABLE_LIBCLAMAV_ONLY)
find_package(CURL REQUIRED)
@ -239,6 +257,7 @@ if(NOT ENABLE_LIBCLAMAV_ONLY)
set(HAVE_SYSTEMD 1)
endif()
endif()
endif()
endif()
@ -284,9 +303,6 @@ endif()
include(GNUInstallDirs)
# For test scripts and documentation
find_package(Python3)
# Always use '-fPIC'/'-fPIE' option.
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
@ -342,6 +358,12 @@ check_include_file("sys/inttypes.h" HAVE_SYS_INTTYPES_H)
check_include_file("sys/int_types.h" HAVE_SYS_INT_TYPES_H)
check_include_file("stdint.h" HAVE_STDINT_H)
# this hack required to silence warnings on systems with inttypes.h
# because json-c's inttypes header isn't generated for each system. Hooray C!
if(HAVE_INTTYPES_H)
set(JSON_C_HAVE_INTTYPES_H 1)
endif()
include(CheckTypeSize)
# Checks for typedefs, structures, and compiler characteristics.
# AC_TYPE_SIZE_T
@ -730,6 +752,13 @@ if(ENABLE_EXAMPLES)
add_subdirectory( examples )
endif()
include(CTest)
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
if(ENABLE_TESTS)
add_subdirectory(test)
add_subdirectory(unit_tests)
endif()
if(ENABLE_DOCS)
add_subdirectory( docs )
endif()
@ -760,6 +789,7 @@ ${b} Shared library: ${e}${ENABLE_SHARED_LIB}
${b} Static library: ${e}${ENABLE_STATIC_LIB}
${b} Enable UnRAR: ${e}${ENABLE_UNRAR}
${b} Examples: ${e}${ENABLE_EXAMPLES}
${b} Tests: ${e}${ENABLE_TESTS}
${b} Build man pages: ${e}${ENABLE_DOCS}
${b} Build doxygen HTML: ${e}${ENABLE_DOXYGEN}")
if(NOT WIN32)
@ -784,7 +814,14 @@ message(STATUS "${C}Engine Options${e} --
${b} Bytecode Runtime: ${e}
${_} ${BYTECODE_RUNTIME} ${e}")
endif()
if(ENABLE_TESTS)
message(STATUS "${C}Test Dependencies${e} --
${b} Unit Test Framework:${e}
${_} libcheck ${e}${LIBCHECK_INCLUDE_DIR}
${_} ${e}${LIBCHECK_LIBRARY}
${b} Feature Test Framework:${e}
${_} python3 ${e}${Python3_EXECUTABLE}")
endif()
message(STATUS "${C}libclamav Dependencies${e} --
${b} Compression support:${e}
${_} bzip2 ${e}${BZIP2_INCLUDE_DIRS}

@ -89,6 +89,10 @@ option(ENABLE_EXAMPLES
"Build examples."
${ENABLE_EXAMPLES_DEFAULT})
option(ENABLE_TESTS
"Build/enable unit tests."
${ENABLE_TESTS_DEFAULT})
option(ENABLE_LIBCLAMAV_ONLY
"Build libclamav only. Excludes libfreshclam too!")

@ -38,6 +38,7 @@ _Known Issues / To-do:_
- [libfreshclam dependencies](#libfreshclam-dependencies)
- [Application dependencies](#application-dependencies)
- [Dependency build options](#dependency-build-options)
- [libcheck](#libcheck)
- [bzip2](#bzip2)
- [zlib](#zlib)
- [libxml2](#libxml2)
@ -95,6 +96,12 @@ cmake .. -DCMAKE_BUILD_TYPE="Debug" -DOPTIMIZE=OFF
cmake --build . --config Debug
```
_Tip_: CMake provides four `CMAKE_BUILD_TYPE`s / options for `--config`):
- Debug
- Release
- MinSizeRel
- RelWithDebInfo
### Build and install to a specific install location (prefix)
```sh
@ -113,7 +120,14 @@ cmake --build . --config Release
### Build and run tests
_TODO_: We have not yet added unit test support for CMake.
The option to build and run tests is enabled by default, which requires that
you provide libcheck `check`, `check-devel`, `check-dev`, etc.
If you're building with `ENABLE_LIBCLAMAV_ONLY=ON` or `ENABLE_APP=OFF`, then
libcheck will still be required and you can still run the tests, but it will
skip all app tests and only run the libclamav unit tests.
If you wish to disable test support, then configure with `-DENABLE_TESTS=OFF`.
- `-V`: Verbose
@ -228,6 +242,10 @@ cmake --build . --config Debug
_Default: `OFF`_
- `ENABLE_TESTS`: Build examples.
_Default: `ON`_
- `ENABLE_LIBCLAMAV_ONLY`: Build libclamav only. Excludes libfreshclam too!
_Default: `OFF`_
@ -366,6 +384,7 @@ $env:CLAMAV_DEPENDENCIES="$env:userprofile\.mussels\install\x64"
cmake .. -G "Visual Studio 15 2017" -A x64 `
-DJSONC_INCLUDE_DIR="$env:CLAMAV_DEPENDENCIES\include\json-c" `
-DJSONC_LIBRARY="$env:CLAMAV_DEPENDENCIES\lib\json-c.lib" `
-DENABLE_JSON_SHARED=OFF `
-DBZIP2_INCLUDE_DIR="$env:CLAMAV_DEPENDENCIES\include" `
-DBZIP2_LIBRARY_RELEASE="$env:CLAMAV_DEPENDENCIES\lib\libbz2.lib" `
-DCURL_INCLUDE_DIR="$env:CLAMAV_DEPENDENCIES\include" `
@ -384,6 +403,8 @@ cmake .. -G "Visual Studio 15 2017" -A x64 `
-DPThreadW32_LIBRARY="$env:CLAMAV_DEPENDENCIES\lib\pthreadVC2.lib" `
-DZLIB_INCLUDE_DIR="$env:CLAMAV_DEPENDENCIES\include" `
-DZLIB_LIBRARY="$env:CLAMAV_DEPENDENCIES\lib\zlibstatic.lib" `
-DLIBCHECK_INCLUDE_DIR="$env:CLAMAV_DEPENDENCIES\include" `
-DLIBCHECK_LIBRARY="$env:CLAMAV_DEPENDENCIES\lib\checkDynamic.lib" `
-DCMAKE_INSTALL_PREFIX="install"
cmake --build . --config Release --target install
copy $env:CLAMAV_DEPENDENCIES\lib\* .\install
@ -441,6 +462,14 @@ For regular folk who want the ClamAV apps, you'll also need:
If you have custom install paths for the dependencies on your system or are
on Windows, you may need to use the following options...
##### libcheck
```sh
-DLIBCHECK_ROOT_DIR="_path to libcheck install root_"
-DLIBCHECK_INCLUDE_DIR="_filepath of libcheck header directory_"
-DLIBCHECK_LIBRARY="_filepath of libcheck library_"
```
##### bzip2
```sh

@ -178,6 +178,9 @@
/* Define to 1 if you have the <inttypes.h> header file. */
#cmakedefine HAVE_INTTYPES_H 1
/* Define to 1 if you have the <inttypes.h> header file (for libjson-c). */
#cmakedefine JSON_C_HAVE_INTTYPES_H 1
/* Define to 1 if you have the 'libjson' library (-ljson). */
#cmakedefine HAVE_JSON 1
@ -384,7 +387,7 @@
#cmakedefine HAVE_WORKING_ARGZ 1
/* yara sources are compiled in */
#cmakedefine HAVE_YARA 1
#define HAVE_YARA 1
/* Define to 1 if you have the <zlib.h> header file. */
#cmakedefine HAVE_ZLIB_H 1

@ -1,7 +1,5 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
# The clamav-milter executable.
add_executable( clamav-milter )
target_sources( clamav-milter

@ -1,10 +1,11 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
# Windows compatibility headers
include_directories(${CMAKE_SOURCE_DIR}/win32/compat)

@ -1,10 +1,11 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
# Windows compatibility headers
include_directories(${CMAKE_SOURCE_DIR}/win32/compat)

@ -1,10 +1,11 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
# Windows compatibility headers
include_directories(${CMAKE_SOURCE_DIR}/win32/compat)

@ -80,7 +80,6 @@
#include <sys/wait.h>
#endif
short debug_mode = 0, logok = 0;
short foreground = -1;
@ -97,6 +96,7 @@ static void help(void)
printf(" --version -V Show version number\n");
printf(" --foreground -F Run in foreground; do not daemonize\n");
printf(" --debug Enable debug mode\n");
printf(" --log=FILE -l FILE Log into FILE\n");
printf(" --config-file=FILE -c FILE Read configuration from FILE\n");
printf("\n");
printf("Pass in - as the filename for stdin.\n");
@ -144,9 +144,9 @@ int main(int argc, char **argv)
#ifdef C_LINUX
STATBUF sb;
#endif
pid_t mainpid = 0;
mode_t old_umask = 0;
const char * user_name = NULL;
pid_t mainpid = 0;
mode_t old_umask = 0;
const char *user_name = NULL;
if (check_flevel())
exit(1);
@ -156,8 +156,8 @@ int main(int argc, char **argv)
sa.sa_handler = SIG_IGN;
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);
if(!setlocale(LC_CTYPE, "")) {
mprintf("^Failed to set locale\n");
if (!setlocale(LC_CTYPE, "")) {
mprintf("^Failed to set locale\n");
}
#endif
@ -214,7 +214,7 @@ int main(int argc, char **argv)
}
free(pt);
if ((opt = optget(opts, "User"))->enabled){
if ((opt = optget(opts, "User"))->enabled) {
user_name = opt->strarg;
}
@ -252,48 +252,47 @@ int main(int argc, char **argv)
logg_file = NULL;
}
#ifndef WIN32
/* fork into background */
if (foreground == -1) {
if (optget(opts, "Foreground")->enabled) {
foreground = 1;
} else {
foreground = 0;
}
/* fork into background */
if (foreground == -1) {
if (optget(opts, "Foreground")->enabled) {
foreground = 1;
} else {
foreground = 0;
}
if (foreground == 0) {
int daemonizeRet = 0;
}
if (foreground == 0) {
int daemonizeRet = 0;
#ifdef C_BSD
/* workaround for OpenBSD bug, see https://wwws.clamav.net/bugzilla/show_bug.cgi?id=885 */
for (ret = 0; (unsigned int)ret < nlsockets; ret++) {
if (fcntl(lsockets[ret], F_SETFL, fcntl(lsockets[ret], F_GETFL) | O_NONBLOCK) == -1) {
logg("!fcntl for lsockets[] failed\n");
close(lsockets[ret]);
ret = 1;
break;
}
/* workaround for OpenBSD bug, see https://wwws.clamav.net/bugzilla/show_bug.cgi?id=885 */
for (ret = 0; (unsigned int)ret < nlsockets; ret++) {
if (fcntl(lsockets[ret], F_SETFL, fcntl(lsockets[ret], F_GETFL) | O_NONBLOCK) == -1) {
logg("!fcntl for lsockets[] failed\n");
close(lsockets[ret]);
ret = 1;
break;
}
}
#endif
gengine = engine;
atexit(free_engine);
daemonizeRet = daemonize_parent_wait(user_name, logg_file);
if (daemonizeRet < 0){
logg("!daemonize() failed: %s\n", strerror(errno));
return 1;
}
gengine = NULL;
gengine = engine;
atexit(free_engine);
daemonizeRet = daemonize_parent_wait(user_name, logg_file);
if (daemonizeRet < 0) {
logg("!daemonize() failed: %s\n", strerror(errno));
return 1;
}
gengine = NULL;
#ifdef C_BSD
for (ret = 0; (unsigned int)ret < nlsockets; ret++) {
if (fcntl(lsockets[ret], F_SETFL, fcntl(lsockets[ret], F_GETFL) & ~O_NONBLOCK) == -1) {
logg("!fcntl for lsockets[] failed\n");
close(lsockets[ret]);
ret = 1;
break;
}
for (ret = 0; (unsigned int)ret < nlsockets; ret++) {
if (fcntl(lsockets[ret], F_SETFL, fcntl(lsockets[ret], F_GETFL) & ~O_NONBLOCK) == -1) {
logg("!fcntl for lsockets[] failed\n");
close(lsockets[ret]);
ret = 1;
break;
}
#endif
}
#endif
}
#endif
@ -321,10 +320,10 @@ int main(int argc, char **argv)
/*If the file has already been created by a different user, it will just be
* rewritten by us, but not change the ownership, so do that explicitly.
*/
if (0 == geteuid()){
struct passwd * pw = getpwuid(0);
int ret = lchown(opt->strarg, pw->pw_uid, pw->pw_gid);
if (ret){
if (0 == geteuid()) {
struct passwd *pw = getpwuid(0);
int ret = lchown(opt->strarg, pw->pw_uid, pw->pw_gid);
if (ret) {
logg("!Can't change ownership of PID file %s '%s'\n", opt->strarg, strerror(errno));
exit(2);
}
@ -782,7 +781,7 @@ int main(int argc, char **argv)
* now, since everything is initialized.*/
/*signal the parent process.*/
if (parentPid != getpid()){
if (parentPid != getpid()) {
daemonize_signal_parent(parentPid);
}
#endif

@ -1,10 +1,11 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
# Windows compatibility headers
include_directories(${CMAKE_SOURCE_DIR}/win32/compat)

@ -1,10 +1,11 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
# Windows compatibility headers
include_directories(${CMAKE_SOURCE_DIR}/win32/compat)
@ -29,6 +30,29 @@ target_link_libraries( clamdtop
Curses::curses )
if(WIN32)
install(TARGETS clamdtop DESTINATION ${CMAKE_INSTALL_PREFIX})
# Also install shared library (DLL) dependencies
install(CODE [[
file(GET_RUNTIME_DEPENDENCIES
EXECUTABLES
$<TARGET_FILE:clamdtop>
RESOLVED_DEPENDENCIES_VAR _r_deps
UNRESOLVED_DEPENDENCIES_VAR _u_deps
DIRECTORIES
$<TARGET_FILE_DIR:Curses::curses>
)
foreach(_file ${_r_deps})
string(TOLOWER ${_file} _file_lower)
if(NOT ${_file_lower} MATCHES "c:[\\/]windows[\\/]system32.*")
file(INSTALL
DESTINATION "${CMAKE_INSTALL_PREFIX}"
TYPE SHARED_LIBRARY
FOLLOW_SYMLINK_CHAIN
FILES "${_file}"
)
endif()
endforeach()
#message("UNRESOLVED_DEPENDENCIES_VAR: ${_u_deps}")
]])
else()
install(TARGETS clamdtop DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()

@ -1,7 +1,5 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
# The clamonacc executable.
add_executable( clamonacc )
target_sources( clamonacc
@ -49,7 +47,8 @@ set_target_properties( clamonacc PROPERTIES COMPILE_FLAGS "${WARNCFLAGS}" )
target_link_libraries( clamonacc
PRIVATE
ClamAV::libclamav
ClamAV::shared )
ClamAV::shared
CURL::libcurl )
install(TARGETS clamonacc DESTINATION ${CMAKE_INSTALL_SBINDIR})
if(SYSTEMD_FOUND)

@ -1,10 +1,11 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
# Windows compatibility headers
include_directories(${CMAKE_SOURCE_DIR}/win32/compat)

@ -1,10 +1,11 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
# Windows compatibility headers
include_directories(${CMAKE_SOURCE_DIR}/win32/compat)
@ -26,7 +27,8 @@ target_link_libraries( clamsubmit
PRIVATE
ClamAV::libclamav
ClamAV::shared
JSONC::jsonc )
JSONC::jsonc
CURL::libcurl )
if(APPLE)
target_link_libraries( clamsubmit
PUBLIC
@ -35,6 +37,30 @@ if(APPLE)
endif()
if(WIN32)
install(TARGETS clamsubmit DESTINATION ${CMAKE_INSTALL_PREFIX})
# Also install shared library (DLL) dependencies
install(CODE [[
file(GET_RUNTIME_DEPENDENCIES
EXECUTABLES
$<TARGET_FILE:clamsubmit>
RESOLVED_DEPENDENCIES_VAR _r_deps
UNRESOLVED_DEPENDENCIES_VAR _u_deps
DIRECTORIES
$<TARGET_FILE_DIR:CURL::libcurl>
$<TARGET_FILE_DIR:JSONC::jsonc>
)
foreach(_file ${_r_deps})
string(TOLOWER ${_file} _file_lower)
if(NOT ${_file_lower} MATCHES "c:[\\/]windows[\\/]system32.*")
file(INSTALL
DESTINATION "${CMAKE_INSTALL_PREFIX}"
TYPE SHARED_LIBRARY
FOLLOW_SYMLINK_CHAIN
FILES "${_file}"
)
endif()
endforeach()
#message("UNRESOLVED_DEPENDENCIES_VAR: ${_u_deps}")
]])
else()
install(TARGETS clamsubmit DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()

@ -1,3 +1,29 @@
/*
* ClamAV Malware and False Positive Reporting Tool
*
* Copyright (C) 2014-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*
* Authors: Shawn Webb, Steve Morgan
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#if HAVE_UNISTD_H
@ -11,7 +37,7 @@
#endif
#include <curl/curl.h>
#include <json-c/json.h>
#include <json.h>
#include "target.h"

@ -0,0 +1,124 @@
# Copyright 2019 Collabora, Ltd.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
#
# Original Author:
# 2019 Ryan Pavlik <ryan.pavlik@collabora.com>
#.rst:
# FindCheck
# ---------------
#
# Find the "Check" C unit testing framework.
#
# See https://libcheck.github.io
#
# The Debian package for this is called ``check``
#
# Targets
# ^^^^^^^
#
# If successful, the following imported targets are created.
#
# ``libcheck::check``
#
# Cache variables
# ^^^^^^^^^^^^^^^
#
# The following cache variable may also be set to assist/control the operation of this module:
#
# ``LIBCHECK_ROOT_DIR``
# The root to search for libcheck.
set(LIBCHECK_ROOT_DIR "${LIBCHECK_ROOT_DIR}" CACHE PATH "Root to search for libcheck")
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
set(_old_prefix_path "${CMAKE_PREFIX_PATH}")
# So pkg-config uses LIBCHECK_ROOT_DIR too.
if(LIBCHECK_ROOT_DIR)
list(APPEND CMAKE_PREFIX_PATH ${LIBCHECK_ROOT_DIR})
endif()
pkg_check_modules(PC_LIBCHECK QUIET check)
# Restore
set(CMAKE_PREFIX_PATH "${_old_prefix_path}")
endif()
find_path(LIBCHECK_INCLUDE_DIR
NAMES
check.h
PATHS
${LIBCHECK_ROOT_DIR}
HINTS
${PC_LIBCHECK_INCLUDE_DIRS}
PATH_SUFFIXES
include
)
find_library(LIBCHECK_LIBRARY
NAMES
check_pic
check
PATHS
${LIBCHECK_ROOT_DIR}
HINTS
${PC_LIBCHECK_LIBRARY_DIRS}
PATH_SUFFIXES
lib
)
find_library(LIBCHECK_SUBUNIT_LIBRARY
NAMES
subunit
PATHS
${LIBCHECK_ROOT_DIR}
HINTS
${PC_LIBCHECK_LIBRARY_DIRS}
PATH_SUFFIXES
lib
)
find_library(LIBCHECK_LIBRT rt)
find_library(LIBCHECK_LIBM m)
find_package(Threads QUIET)
set(_libcheck_extra_required)
if(PC_LIBCHECK_FOUND AND "${PC_LIBCHECK_LIBRARIES}" MATCHES "subunit")
list(APPEND _libcheck_extra_required LIBCHECK_SUBUNIT_LIBRARY)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Libcheck
REQUIRED_VARS
LIBCHECK_INCLUDE_DIR
LIBCHECK_LIBRARY
THREADS_FOUND
)
if(LIBCHECK_FOUND)
if(NOT TARGET libcheck::check)
add_library(libcheck::check UNKNOWN IMPORTED)
set_target_properties(libcheck::check PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${LIBCHECK_INCLUDE_DIR}")
set_target_properties(libcheck::check PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION ${LIBCHECK_LIBRARY})
set_property(TARGET libcheck::check PROPERTY
IMPORTED_LINK_INTERFACE_LIBRARIES Threads::Threads)
# if we found librt or libm, link them.
if(LIBCHECK_LIBRT)
set_property(TARGET libcheck::check APPEND PROPERTY
IMPORTED_LINK_INTERFACE_LIBRARIES "${LIBCHECK_LIBRT}")
endif()
if(LIBCHECK_LIBM)
set_property(TARGET libcheck::check APPEND PROPERTY
IMPORTED_LINK_INTERFACE_LIBRARIES "${LIBCHECK_LIBM}")
endif()
if(LIBCHECK_SUBUNIT_LIBRARY)
set_property(TARGET libcheck::check APPEND PROPERTY
IMPORTED_LINK_INTERFACE_LIBRARIES "${LIBCHECK_SUBUNIT_LIBRARY}")
endif()
endif()
mark_as_advanced(LIBCHECK_INCLUDE_DIR LIBCHECK_LIBRARY LIBCHECK_SUBUNIT_LIBRARY)
endif()
mark_as_advanced(LIBCHECK_ROOT_DIR LIBCHECK_LIBRT LIBCHECK_LIBM)

@ -0,0 +1,36 @@
#
# Find the Valgrind program.
#
# If found, will set: Valgrind_FOUND, Valgrind_VERSION, and Valgrind_EXECUTABLE
#
# If you have a custom install location for Valgrind, you can provide a hint
# by settings -DValgrind_HOME=<directory containing valgrind>
#
find_program(Valgrind_EXECUTABLE valgrind
HINTS "${Valgrind_HOME}"
PATH_SUFFIXES "bin"
)
if(Valgrind_EXECUTABLE)
execute_process(COMMAND "${Valgrind_EXECUTABLE}" --version
OUTPUT_VARIABLE Valgrind_VERSION_OUTPUT
ERROR_VARIABLE Valgrind_VERSION_ERROR
RESULT_VARIABLE Valgrind_VERSION_RESULT
)
if(NOT ${Valgrind_VERSION_RESULT} EQUAL 0)
message(STATUS "Valgrind not found: Failed to determine version.")
unset(Valgrind_EXECUTABLE)
else()
string(REGEX
MATCH "[0-9]+\\.[0-9]+(\\.[0-9]+)?(-nightly)?"
Valgrind_VERSION "${Valgrind_VERSION_OUTPUT}"
)
set(Valgrind_VERSION "${Valgrind_VERSION}")
set(Valgrind_FOUND 1)
message(STATUS "Valgrind found: ${Valgrind_EXECUTABLE}, ${Valgrind_VERSION}")
endif()
mark_as_advanced(Valgrind_EXECUTABLE Valgrind_VERSION)
else()
message(STATUS "Valgrind not found.")
endif()

@ -1,7 +1,5 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
#
# Example executables
#

@ -1,10 +1,11 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
# Windows compatibility headers
include_directories(${CMAKE_SOURCE_DIR}/win32/compat)

@ -1,13 +1,11 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
include_directories(
${LIBXML2_INCLUDE_DIR}
${OPENSSL_INCLUDE_DIR}
${ZLIB_INCLUDE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
$<TARGET_PROPERTY:ClamAV::libclamunrar_iface_iface,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:ClamAV::libunrar_iface_iface,INTERFACE_INCLUDE_DIRECTORIES>
${CMAKE_CURRENT_SOURCE_DIR}/..
)
@ -16,6 +14,9 @@ configure_file(version.h.in version.h)
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
# Windows compatibility headers
include_directories(${CMAKE_SOURCE_DIR}/win32/compat)
@ -39,8 +40,8 @@ target_sources( regex
target_include_directories( regex
PRIVATE ${CMAKE_BINARY_DIR}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} )
set_target_properties(regex PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}")
set_target_properties( regex PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}" )
target_link_libraries( regex
PRIVATE
PCRE2::pcre2
@ -96,8 +97,8 @@ target_sources( lzma_sdk
target_include_directories( lzma_sdk
PRIVATE ${CMAKE_BINARY_DIR}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} )
set_target_properties(lzma_sdk PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}")
set_target_properties( lzma_sdk PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}" )
target_link_libraries( lzma_sdk
PRIVATE
PCRE2::pcre2
@ -108,7 +109,7 @@ if(MAINTAINER_MODE)
yara_grammar.y ${CMAKE_CURRENT_SOURCE_DIR}/yara_grammar.c )
flex_target( yara_lexer
yara_lexer.l ${CMAKE_CURRENT_SOURCE_DIR}/yara_lexer.c )
add_flex_bison_dependency(yara_lexer yara_grammar)
add_flex_bison_dependency( yara_lexer yara_grammar )
endif()
add_library( yara OBJECT )
@ -148,8 +149,8 @@ target_include_directories( yara
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR} )
set_target_properties(yara PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}")
set_target_properties( yara PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}" )
target_link_libraries( yara
PRIVATE
PCRE2::pcre2
@ -245,8 +246,8 @@ target_sources( tomsfastmath
target_include_directories( tomsfastmath
PRIVATE ${CMAKE_BINARY_DIR}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} )
set_target_properties(tomsfastmath PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}")
set_target_properties( tomsfastmath PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}" )
# Bytecode Runtime
add_library( bytecode_runtime OBJECT )
@ -258,16 +259,16 @@ if(LLVM_FOUND)
c++/bytecode2llvm.cpp
bytecode_priv.h
bytecode.h )
set_target_properties(bytecode_runtime PROPERTIES
COMPILE_FLAGS "${WARNCXXFLAGS} ${CXX1XCXXFLAGS}")
set_target_properties( bytecode_runtime PROPERTIES
COMPILE_FLAGS "${WARNCXXFLAGS} ${CXX1XCXXFLAGS}" )
else()
target_sources( bytecode_runtime
PRIVATE
bytecode_nojit.c
bytecode_priv.h
bytecode.h )
set_target_properties(bytecode_runtime PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}")
set_target_properties( bytecode_runtime PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}" )
endif()
target_include_directories( bytecode_runtime
PRIVATE ${CMAKE_BINARY_DIR}
@ -505,7 +506,7 @@ target_link_libraries( clamav_obj
tomsfastmath
bytecode_runtime
ClamAV::libmspack
ClamAV::libclamunrar_iface_iface
ClamAV::libunrar_iface_iface
OpenSSL::SSL
OpenSSL::Crypto
ZLIB::ZLIB
@ -527,15 +528,15 @@ else()
${CMAKE_DL_LIBS}
m )
endif()
set_target_properties(clamav_obj PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}")
set_target_properties( clamav_obj PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}" )
if(ENABLE_SHARED_LIB)
# The clamav shared library.
add_library( clamav SHARED )
set_target_properties(clamav PROPERTIES
set_target_properties( clamav PROPERTIES
VERSION ${LIBCLAMAV_VERSION}
SOVERSION ${LIBCLAMAV_SOVERSION})
SOVERSION ${LIBCLAMAV_SOVERSION} )
target_sources( clamav
PUBLIC
clamav.h )
@ -548,7 +549,7 @@ if(ENABLE_SHARED_LIB)
tomsfastmath
bytecode_runtime
ClamAV::libmspack
ClamAV::libclamunrar_iface_iface
ClamAV::libunrar_iface_iface
OpenSSL::SSL
OpenSSL::Crypto
ZLIB::ZLIB
@ -556,18 +557,48 @@ if(ENABLE_SHARED_LIB)
PCRE2::pcre2
LibXml2::LibXml2
JSONC::jsonc )
set_target_properties(clamav PROPERTIES
set_target_properties( clamav PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}"
VERSION ${LIBCLAMAV_VERSION} SOVERSION ${LIBCLAMAV_SOVERSION})
VERSION ${LIBCLAMAV_VERSION} SOVERSION ${LIBCLAMAV_SOVERSION} )
if(WIN32)
set_target_properties(clamav PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
set_target_properties( clamav PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON )
else()
target_link_libraries( clamav PUBLIC ICONV::Iconv )
endif()
if(WIN32)
install(TARGETS clamav DESTINATION ${CMAKE_INSTALL_PREFIX})
install( TARGETS clamav DESTINATION ${CMAKE_INSTALL_PREFIX} )
# Also install shared library (DLL) dependencies
install( CODE [[
file(GET_RUNTIME_DEPENDENCIES
LIBRARIES
$<TARGET_FILE:ClamAV::libclamav>
RESOLVED_DEPENDENCIES_VAR _r_deps
UNRESOLVED_DEPENDENCIES_VAR _u_deps
DIRECTORIES
$<TARGET_FILE_DIR:OpenSSL::SSL>
$<TARGET_FILE_DIR:OpenSSL::Crypto>
$<TARGET_FILE_DIR:ZLIB::ZLIB>
$<TARGET_FILE_DIR:BZip2::BZip2>
$<TARGET_FILE_DIR:PCRE2::pcre2>
$<TARGET_FILE_DIR:LibXml2::LibXml2>
$<TARGET_FILE_DIR:JSONC::jsonc>
)
foreach(_file ${_r_deps})
string(TOLOWER ${_file} _file_lower)
if(NOT ${_file_lower} MATCHES "c:[\\/]windows[\\/]system32.*")
file(INSTALL
DESTINATION "${CMAKE_INSTALL_PREFIX}"
TYPE SHARED_LIBRARY
FOLLOW_SYMLINK_CHAIN
FILES "${_file}"
)
endif()
endforeach()
#message("UNRESOLVED_DEPENDENCIES_VAR: ${_u_deps}")
]] )
else()
install(TARGETS clamav DESTINATION ${CMAKE_INSTALL_LIBDIR})
install( TARGETS clamav DESTINATION ${CMAKE_INSTALL_LIBDIR} )
endif()
if(LLVM_FOUND)
@ -580,11 +611,11 @@ endif()
if(ENABLE_STATIC_LIB)
# The clamav static library.
add_library(clamav_static STATIC)
target_sources(clamav_static
add_library( clamav_static STATIC)
target_sources( clamav_static
PUBLIC
clamav.h )
target_link_libraries(clamav_static
target_link_libraries( clamav_static
PUBLIC
clamav_obj
regex
@ -593,7 +624,7 @@ if(ENABLE_STATIC_LIB)
tomsfastmath
bytecode_runtime
ClamAV::libmspack
ClamAV::libclamunrar_iface_iface
ClamAV::libunrar_iface_iface
OpenSSL::SSL
OpenSSL::Crypto
ZLIB::ZLIB
@ -604,15 +635,15 @@ if(ENABLE_STATIC_LIB)
if(NOT WIN32)
target_link_libraries( clamav PUBLIC ICONV::Iconv )
endif()
set_target_properties(clamav_static PROPERTIES
set_target_properties( clamav_static PROPERTIES
ARCHIVE_OUTPUT_NAME clamav_static
COMPILE_FLAGS "${WARNCFLAGS}"
VERSION ${LIBCLAMAV_VERSION} SOVERSION ${LIBCLAMAV_SOVERSION})
target_compile_definitions(clamav_static PUBLIC clamav_staticLIB)
VERSION ${LIBCLAMAV_VERSION} SOVERSION ${LIBCLAMAV_SOVERSION} )
target_compile_definitions( clamav_static PUBLIC clamav_staticLIB )
if(WIN32)
install(TARGETS clamav_static DESTINATION ${CMAKE_INSTALL_PREFIX})
install( TARGETS clamav_static DESTINATION ${CMAKE_INSTALL_PREFIX} )
else()
install(TARGETS clamav_static DESTINATION ${CMAKE_INSTALL_LIBDIR})
install( TARGETS clamav_static DESTINATION ${CMAKE_INSTALL_LIBDIR} )
endif()
add_library( ClamAV::libclamav_static ALIAS clamav_static )

@ -1847,8 +1847,7 @@ int cli_bytecode_run(const struct cli_all_bc *bcs, const struct cli_bc *bc, stru
cli_event_int(interp_ev, BCEV_EXEC_RETURNVALUE, ret);
cli_event_string(interp_ev, BCEV_VIRUSNAME, ctx->virname);
/* need to be called here to catch any extracted but not yet scanned files
*/
/* need to be called here to catch any extracted but not yet scanned files */
if (ctx->outfd && (ret != CL_VIRUS || cctx->options->general & CL_SCAN_GENERAL_ALLMATCHES))
cli_bcapi_extract_new(ctx, -1);
}
@ -1867,8 +1866,7 @@ int cli_bytecode_run(const struct cli_all_bc *bcs, const struct cli_bc *bc, stru
cli_event_int(jit_ev, BCEV_EXEC_RETURNVALUE, ret);
cli_event_string(jit_ev, BCEV_VIRUSNAME, ctx->virname);
/* need to be called here to catch any extracted but not yet scanned files
*/
/* need to be called here to catch any extracted but not yet scanned files */
if (ctx->outfd && (ret != CL_VIRUS || cctx->options->general & CL_SCAN_GENERAL_ALLMATCHES))
cli_bcapi_extract_new(ctx, -1);
}

@ -112,7 +112,7 @@ const char* entity_norm(struct entity_conv* conv, const unsigned char* entity)
{
struct cli_element* e = cli_hashtab_find(&entities_htable, (const char*)entity, strlen((const char*)entity));
if (e && e->key) {
unsigned char* out = u16_normalize(e->data, conv->entity_buff, sizeof(conv->entity_buff) - 1);
unsigned char* out = u16_normalize((uint16_t)e->data, conv->entity_buff, sizeof(conv->entity_buff) - 1);
if (out) {
*out++ = '\0';
return (const char*)conv->entity_buff;
@ -127,7 +127,7 @@ static size_t encoding_bytes(const char* fromcode, enum encodings* encoding)
/* special case for these unusual byteorders */
struct cli_element* e = cli_hashtab_find(&aliases_htable, fromcode, strlen(fromcode));
if (e && e->key) {
*encoding = e->data;
*encoding = (enum encodings)e->data;
} else {
*encoding = E_OTHER;
}
@ -639,14 +639,14 @@ static iconv_t iconv_open_cached(const char* fromcode)
}
e = cli_hashtab_find(&cache->hashtab, fromcode, fromcode_len);
if (e && (e->data < 0 || (size_t)e->data > cache->len)) {
if (e && ((size_t)e->data == (size_t)-1 || (size_t)e->data > cache->len)) {
e = NULL;
}
if (e) {
size_t dummy_in, dummy_out;
/* reset state */
iconv(cache->tab[e->data], NULL, &dummy_in, NULL, &dummy_out);
return cache->tab[e->data];
iconv(cache->tab[(size_t)e->data], NULL, &dummy_in, NULL, &dummy_out);
return cache->tab[(size_t)e->data];
}
cli_dbgmsg(MODULE_NAME "iconv not found in cache, for encoding:%s\n", fromcode);
iconv_struct = iconv_open("UTF-16BE", (const char*)fromcode);
@ -664,7 +664,7 @@ static iconv_t iconv_open_cached(const char* fromcode)
}
}
cli_hashtab_insert(&cache->hashtab, fromcode, fromcode_len, idx);
cli_hashtab_insert(&cache->hashtab, fromcode, fromcode_len, (const cli_element_data)idx);
cache->tab[idx] = iconv_struct;
cli_dbgmsg(MODULE_NAME "iconv_open(),for:%s -> %p\n", fromcode, (void*)cache->tab[idx]);
return cache->tab[idx];
@ -850,41 +850,70 @@ cl_error_t cli_codepage_to_utf8(char* in, size_t in_size, uint16_t codepage, cha
/*
* First, Convert from codepage -> UCS-2 LE with MultiByteToWideChar(codepage)
*/
cchWideChar = MultiByteToWideChar(
codepage,
0,
in,
in_size,
NULL,
0);
if (0 == cchWideChar) {
cli_dbgmsg("cli_codepage_to_utf8: failed to determine string size needed for ansi to widechar conversion.\n");
status = CL_EPARSE;
goto done;
}
if (CODEPAGE_UTF16_BE == codepage) {
/*
* MultiByteToWideChar() does not support conversions from UTF-16BE to UTF-16LE.
* However, conversion is simply a matter of swapping the bytes for each WCHAR_T.
* See: https://stackoverflow.com/questions/29054217/multibytetowidechar-for-unicode-code-pages-1200-1201-12000-12001
*/
int i = 0;
uint16_t* pCodeUnits = (uint16_t*)in;
cchWideChar = (int)in_size / 2;
lpWideCharStr = cli_malloc((cchWideChar) * sizeof(WCHAR)); /* No need for a null terminator here, we'll deal with the exact size */
if (NULL == lpWideCharStr) {
cli_dbgmsg("cli_codepage_to_utf8: failed to allocate memory for wide char string.\n");
status = CL_EMEM;
goto done;
}
lpWideCharStr = cli_malloc((cchWideChar + 1) * sizeof(WCHAR));
if (NULL == lpWideCharStr) {
cli_dbgmsg("cli_codepage_to_utf8: failed to allocate memory for wide char string.\n");
status = CL_EMEM;
goto done;
}
for (i = 0; i < cchWideChar; i++) {
lpWideCharStr[i] = (WCHAR)(
((pCodeUnits[i] << 8) & 0xFF00) |
((pCodeUnits[i] >> 8) & 0x00FF));
}
in = (char*)lpWideCharStr;
/* in_size didn't change */
cchWideChar = MultiByteToWideChar(
codepage,
0,
in,
in_size,
lpWideCharStr,
cchWideChar + 1);
if (0 == cchWideChar) {
cli_dbgmsg("cli_codepage_to_utf8: failed to convert multibyte string to widechars.\n");
status = CL_EPARSE;
goto done;
}
} else {
cchWideChar = MultiByteToWideChar(
codepage,
0,
in,
(int)in_size,
NULL,
0);
if (0 == cchWideChar) {
DWORD error = GetLastError();
cli_dbgmsg("cli_codepage_to_utf8: failed to determine string size needed for ansi to widechar conversion: %d.\n", error);
status = CL_EPARSE;
goto done;
}
in = (char*)lpWideCharStr;
in_size = cchWideChar;
lpWideCharStr = cli_malloc((cchWideChar) * sizeof(WCHAR)); /* No need for a null terminator here, we'll deal with the exact size */
if (NULL == lpWideCharStr) {
cli_dbgmsg("cli_codepage_to_utf8: failed to allocate memory for wide char string.\n");
status = CL_EMEM;
goto done;
}
cchWideChar = MultiByteToWideChar(
codepage,
0,
in,
(int)in_size,
lpWideCharStr,
cchWideChar);
if (0 == cchWideChar) {
cli_dbgmsg("cli_codepage_to_utf8: failed to convert multibyte string to widechars.\n");
status = CL_EPARSE;
goto done;
}
in = (char*)lpWideCharStr;
in_size = cchWideChar * sizeof(WCHAR);
}
}
/*
@ -894,7 +923,7 @@ cl_error_t cli_codepage_to_utf8(char* in, size_t in_size, uint16_t codepage, cha
CP_UTF8,
0,
(LPCWCH)in,
in_size / sizeof(WCHAR),
(int)in_size / sizeof(WCHAR),
NULL,
0,
NULL,
@ -905,7 +934,7 @@ cl_error_t cli_codepage_to_utf8(char* in, size_t in_size, uint16_t codepage, cha
goto done;
}
out_utf8 = cli_malloc(out_utf8_size + 1);
out_utf8 = cli_malloc(out_utf8_size + 1); /* Add a null terminator to this string */
if (NULL == out_utf8) {
cli_dbgmsg("cli_codepage_to_utf8: failed to allocate memory for wide char to utf-8 string.\n");
status = CL_EMEM;
@ -916,9 +945,9 @@ cl_error_t cli_codepage_to_utf8(char* in, size_t in_size, uint16_t codepage, cha
CP_UTF8,
0,
(LPCWCH)in,
in_size / sizeof(WCHAR),
(int)in_size / sizeof(WCHAR),
out_utf8,
out_utf8_size,
(int)out_utf8_size,
NULL,
NULL);
if (0 == out_utf8_size) {

@ -326,9 +326,9 @@ static int cli_hashtab_grow(struct cli_hashtable *s)
struct cli_element *htable;
size_t i, idx, used = 0;
cli_dbgmsg("hashtab.c: new capacity: %llu\n", (long long unsigned)new_capacity);
cli_dbgmsg("hashtab.c: new capacity: %zu\n", new_capacity);
if (new_capacity == s->capacity) {
cli_errmsg("hashtab.c: capacity problem growing from: %llu\n", (long long unsigned)s->capacity);
cli_errmsg("hashtab.c: capacity problem growing from: %zu\n", s->capacity);
return CL_EMEM;
}
htable = cli_calloc(new_capacity, sizeof(*s->htable));
@ -368,7 +368,7 @@ static int cli_hashtab_grow(struct cli_hashtable *s)
s->used = used;
s->capacity = new_capacity;
s->maxfill = new_capacity * 8 / 10;
cli_dbgmsg("Table %p size after grow:%llu\n", (void *)s, (long long unsigned)s->capacity);
cli_dbgmsg("Table %p size after grow: %zu\n", (void *)s, s->capacity);
PROFILE_GROW_DONE(s);
return CL_SUCCESS;
}
@ -382,7 +382,7 @@ static int cli_htu32_grow(struct cli_htu32 *s, mpool_t *mempool)
const size_t new_capacity = nearest_power(s->capacity + 1);
struct cli_htu32_element *htable = MPOOL_CALLOC(mempool, new_capacity, sizeof(*s->htable));
size_t i, idx, used = 0;
cli_dbgmsg("hashtab.c: new capacity: %llu\n", (long long unsigned)new_capacity);
cli_dbgmsg("hashtab.c: new capacity: %zu\n", new_capacity);
if (new_capacity == s->capacity || !htable)
return CL_EMEM;
@ -417,7 +417,7 @@ static int cli_htu32_grow(struct cli_htu32 *s, mpool_t *mempool)
s->used = used;
s->capacity = new_capacity;
s->maxfill = new_capacity * 8 / 10;
cli_dbgmsg("Table %p size after grow:%llu\n", (void *)s, (long long unsigned)s->capacity);
cli_dbgmsg("Table %p size after grow: %zu\n", (void *)s, s->capacity);
PROFILE_GROW_DONE(s);
return CL_SUCCESS;
}
@ -431,7 +431,7 @@ const struct cli_element *cli_hashtab_insert(struct cli_hashtable *s, const char
if (!s)
return NULL;
if (s->used > s->maxfill) {
cli_dbgmsg("hashtab.c:Growing hashtable %p, because it has exceeded maxfill, old size:%llu\n", (void *)s, (long long unsigned)s->capacity);
cli_dbgmsg("hashtab.c:Growing hashtable %p, because it has exceeded maxfill, old size: %zu\n", (void *)s, s->capacity);
cli_hashtab_grow(s);
}
do {
@ -476,7 +476,7 @@ const struct cli_element *cli_hashtab_insert(struct cli_hashtable *s, const char
} while (tries <= s->capacity);
/* no free place found*/
PROFILE_HASH_EXHAUSTED(s);
cli_dbgmsg("hashtab.c: Growing hashtable %p, because its full, old size:%llu.\n", (void *)s, (long long unsigned)s->capacity);
cli_dbgmsg("hashtab.c: Growing hashtable %p, because its full, old size: %zu.\n", (void *)s, s->capacity);
} while (cli_hashtab_grow(s) >= 0);
cli_warnmsg("hashtab.c: Unable to grow hashtable\n");
return NULL;
@ -493,7 +493,7 @@ int cli_htu32_insert(struct cli_htu32 *s, const struct cli_htu32_element *item,
if (!s)
return CL_ENULLARG;
if (s->used > s->maxfill) {
cli_dbgmsg("hashtab.c:Growing hashtable %p, because it has exceeded maxfill, old size:%llu\n", (void *)s, (long long unsigned)s->capacity);
cli_dbgmsg("hashtab.c:Growing hashtable %p, because it has exceeded maxfill, old size: %zu\n", (void *)s, s->capacity);
cli_htu32_grow(s, mempool);
}
do {
@ -528,7 +528,7 @@ int cli_htu32_insert(struct cli_htu32 *s, const struct cli_htu32_element *item,
} while (tries <= s->capacity);
/* no free place found*/
PROFILE_HASH_EXHAUSTED(s);
cli_dbgmsg("hashtab.c: Growing hashtable %p, because its full, old size:%llu.\n", (void *)s, (long long unsigned)s->capacity);
cli_dbgmsg("hashtab.c: Growing hashtable %p, because its full, old size: %zu.\n", (void *)s, s->capacity);
} while ((ret = cli_htu32_grow(s, mempool)) >= 0);
cli_warnmsg("hashtab.c: Unable to grow hashtable\n");
return ret;
@ -598,7 +598,7 @@ int cli_hashtab_store(const struct cli_hashtable *s, FILE *out)
for (i = 0; i < s->capacity; i++) {
const struct cli_element *e = &s->htable[i];
if (e->key && e->key != DELETED_KEY) {
fprintf(out, "%ld %s\n", e->data, e->key);
fprintf(out, "%zu %s\n", (size_t)e->data, e->key);
}
}
return CL_SUCCESS;
@ -617,12 +617,11 @@ int cli_hashtab_generate_c(const struct cli_hashtable *s, const char *name)
else if (e->key == DELETED_KEY)
printf("\t{DELETED_KEY,0,0},\n");
else
printf("\t{\"%s\", %ld, %llu},\n", e->key, e->data, (long long unsigned)e->len);
printf("\t{\"%s\", %zu, %zu},\n", e->key, (size_t)e->data, e->len);
}
printf("};\n");
printf("const struct cli_hashtable %s = {\n", name);
printf("\t%s_elements, %llu, %llu, %llu", name, (long long unsigned)s->capacity,
(long long unsigned)s->used, (long long unsigned)s->maxfill);
printf("\t%s_elements, %zu, %zu, %zu", name, s->capacity, s->used, s->maxfill);
printf("\n};\n");
PROFILE_REPORT(s);
@ -634,9 +633,9 @@ int cli_hashtab_load(FILE *in, struct cli_hashtable *s)
char line[1024];
while (fgets(line, sizeof(line), in)) {
char l[1024];
int val;
sscanf(line, "%d %1023s", &val, l);
cli_hashtab_insert(s, l, strlen(l), val);
size_t val;
sscanf(line, "%zu %1023s", &val, l);
cli_hashtab_insert(s, l, strlen(l), (const cli_element_data)val);
}
return CL_SUCCESS;
}
@ -859,13 +858,13 @@ int cli_map_init(struct cli_map *m, int32_t keysize, int32_t valuesize,
int cli_map_addkey(struct cli_map *m, const void *key, int32_t keysize)
{
unsigned n;
uint32_t n;
struct cli_element *el;
if (m->keysize != keysize)
return -CL_EARG;
el = cli_hashtab_find(&m->htab, key, keysize);
if (el) {
m->last_insert = el->data;
m->last_insert = (int32_t)el->data;
return 0;
}
n = m->nvalues + 1;
@ -885,7 +884,7 @@ int cli_map_addkey(struct cli_map *m, const void *key, int32_t keysize)
memset(&m->u.unsized_values[n - 1], 0, sizeof(*m->u.unsized_values));
}
m->nvalues = n;
if (!cli_hashtab_insert(&m->htab, key, keysize, n - 1))
if (!cli_hashtab_insert(&m->htab, key, keysize, (const cli_element_data)(n - 1)))
return -CL_EMEM;
m->last_insert = n - 1;
return 1;
@ -899,15 +898,15 @@ int cli_map_removekey(struct cli_map *m, const void *key, int32_t keysize)
el = cli_hashtab_find(&m->htab, key, keysize);
if (!el)
return 0;
if (el->data >= m->nvalues || el->data < 0)
if ((int32_t)el->data >= (int32_t)m->nvalues || (int32_t)el->data < 0)
return -CL_EARG;
if (!m->valuesize) {
struct cli_map_value *v = &m->u.unsized_values[el->data];
struct cli_map_value *v = &m->u.unsized_values[(int32_t)el->data];
free(v->value);
v->value = NULL;
v->valuesize = 0;
} else {
char *v = (char *)m->u.sized_values + el->data * m->valuesize;
char *v = (char *)m->u.sized_values + (int32_t)el->data * m->valuesize;
memset(v, 0, m->valuesize);
}
cli_hashtab_delete(&m->htab, key, keysize);
@ -944,7 +943,7 @@ int cli_map_find(struct cli_map *m, const void *key, int32_t keysize)
el = cli_hashtab_find(&m->htab, key, keysize);
if (!el)
return 0;
m->last_find = el->data;
m->last_find = (int32_t)el->data;
return 1;
}

@ -34,7 +34,7 @@
#include "clamav-types.h"
#include "clamav-config.h"
#include "mpool.h"
typedef long cli_element_data;
typedef size_t cli_element_data;
/* define this for debugging/profiling purposes only, NOT in production/release code */
#ifdef PROFILE_HASHTABLE
@ -94,7 +94,7 @@ int cli_hashtab_store(const struct cli_hashtable *s, FILE *out);
struct cli_htu32_element {
uint32_t key;
union {
unsigned long as_ulong;
size_t as_size_t;
void *as_ptr;
} data;
};

@ -248,7 +248,7 @@ static struct scope *scope_done(struct scope *s)
static const char *scope_declare(struct scope *s, const char *token, const size_t len, struct parser_state *state)
{
const struct cli_element *el = cli_hashtab_insert(&s->id_map, token, len, state->var_uniq++);
const struct cli_element *el = cli_hashtab_insert(&s->id_map, token, len, (const cli_element_data)(state->var_uniq++));
/* cli_hashtab_insert either finds an already existing entry, or allocates a
* new one, we return the allocated string */
return el ? el->key : NULL;
@ -266,21 +266,21 @@ static const char *scope_use(struct scope *s, const char *token, const size_t le
* Later if we find a declaration it will automatically assign a uniq ID
* to it. If not, we'll know that we have to push ID == -1 tokens to an
* outer scope.*/
el = cli_hashtab_insert(&s->id_map, token, len, -1);
el = cli_hashtab_insert(&s->id_map, token, len, (const cli_element_data)-1);
return el ? el->key : NULL;
}
static long scope_lookup(struct scope *s, const char *token, const size_t len)
static size_t scope_lookup(struct scope *s, const char *token, const size_t len)
{
while (s) {
const struct cli_element *el = cli_hashtab_find(&s->id_map, token, len);
if (el && el->data != -1) {
return el->data;
if (el && (size_t)el->data != (size_t)-1) {
return (size_t)el->data;
}
/* not found in current scope, try in outer scope */
s = s->parent;
}
return -1;
return (size_t)-1;
}
static cl_error_t tokens_ensure_capacity(struct tokens *tokens, size_t cap)
@ -381,12 +381,12 @@ static char output_token(const yystype *token, struct scope *scope, struct buf *
case TOK_IDENTIFIER_NAME:
output_space(lastchar, 'a', out);
if (s) {
long id = scope_lookup(scope, s, strlen(s));
if (id == -1) {
size_t id = scope_lookup(scope, s, strlen(s));
if (id == (size_t)-1) {
/* identifier not normalized */
buf_outs(s, out);
} else {
snprintf(sbuf, sizeof(sbuf), "n%03ld", id);
snprintf(sbuf, sizeof(sbuf), "n%03zu", id);
buf_outs(sbuf, out);
}
}
@ -1660,7 +1660,7 @@ static int parseOperator(YYSTYPE *lvalp, yyscan_t scanner)
{
size_t len = MIN(5, scanner->insize - scanner->pos);
while (len) {
const struct operator*kw = in_op_set(&scanner->in[scanner->pos], len);
const struct operator* kw = in_op_set(&scanner->in[scanner->pos], len);
if (kw) {
TOKEN_SET(lvalp, cstring, kw->name);
scanner->pos += len;

@ -259,6 +259,7 @@ CLAMAV_PRIVATE {
cl_base64_encode;
cli_sanitize_filepath;
cli_gentemp_with_prefix;
cli_gentempfd_with_prefix;
cli_basename;
cli_realpath;
cli_codepage_to_utf8;

@ -187,16 +187,31 @@ static void *load_module(const char *name, const char *featurename)
void *rhandle;
#endif
#ifdef _WIN32
/*
* First try a standard LoadLibraryA() without specifying a full path.
* For more information on the DLL search order, see:
* https://docs.microsoft.com/en-us/windows/desktop/Dlls/dynamic-link-library-search-order
*/
cli_dbgmsg("searching for %s\n", featurename);
#else
/*
* First search using the provided SEARCH_LIBDIR (e.g. "<prefix>/lib")
* Known issue: If an older clamav version is already installed, the clamav
* unit tests using this function will load the older library version from
* the install path first.
*/
searchpath = SEARCH_LIBDIR;
cli_dbgmsg("searching for %s, user-searchpath: %s\n", featurename, searchpath);
for (i = 0; i < sizeof(suffixes) / sizeof(suffixes[0]); i++) {
snprintf(modulename, sizeof(modulename), "%s%s", name, suffixes[i]);
#endif
for (i = 0; i < sizeof(suffixes) / sizeof(suffixes[0]); i++) {
#ifdef _WIN32
snprintf(modulename, sizeof(modulename), "%s%s", name, suffixes[i]);
rhandle = LoadLibraryA(modulename);
#else // !_WIN32
rhandle = dlopen(modulename, RTLD_NOW);
snprintf(modulename, sizeof(modulename), "%s" PATHSEP "%s%s", searchpath, name, suffixes[i]);
rhandle = dlopen(modulename, RTLD_NOW);
#endif // !_WIN32
if (rhandle) {
break;
@ -205,6 +220,49 @@ static void *load_module(const char *name, const char *featurename)
cli_dbgmsg("searching for %s: %s not found\n", featurename, modulename);
}
if (NULL == rhandle) {
char *ld_library_path = NULL;
/*
* library not found.
* Try again using LD_LIBRARY_PATH environment variable for the path.
*/
ld_library_path = getenv("LD_LIBRARY_PATH");
if (NULL != ld_library_path) {
#define MAX_LIBRARY_PATHS 10
size_t token_index;
size_t tokens_count;
const char *tokens[MAX_LIBRARY_PATHS];
char *tokenized_library_path = NULL;
tokenized_library_path = strdup(ld_library_path);
tokens_count = cli_strtokenize(tokenized_library_path, ':', MAX_LIBRARY_PATHS, tokens);
for (token_index = 0; token_index < tokens_count; token_index++) {
cli_dbgmsg("searching for %s, LD_LIBRARY_PATH: %s\n", featurename, tokens[token_index]);
for (i = 0; i < sizeof(suffixes) / sizeof(suffixes[0]); i++) {
snprintf(modulename, sizeof(modulename), "%s" PATHSEP "%s%s", tokens[token_index], name, suffixes[i]);
#ifdef _WIN32
rhandle = LoadLibraryA(modulename);
#else // !_WIN32
rhandle = dlopen(modulename, RTLD_NOW);
#endif // !_WIN32
if (rhandle) {
break;
}
cli_dbgmsg("searching for %s: %s not found\n", featurename, modulename);
}
if (rhandle) {
break;
}
}
free(tokenized_library_path);
}
}
if (NULL == rhandle) {
#ifdef _WIN32
char *err = NULL;

@ -677,11 +677,11 @@ static cl_error_t add_pattern_suffix(void *cbdata, const char *suffix, size_t su
if (el) {
/* existing suffix */
assert((size_t)el->data < matcher->suffix_cnt);
list_add_tail(&matcher->suffix_regexes[el->data], regex);
list_add_tail(&matcher->suffix_regexes[(size_t)el->data], regex);
} else {
/* new suffix */
size_t n = matcher->suffix_cnt++;
el = cli_hashtab_insert(&matcher->suffix_hash, suffix, suffix_len, n);
el = cli_hashtab_insert(&matcher->suffix_hash, suffix, suffix_len, (cli_element_data)n);
tmp_matcher = matcher->suffix_regexes; /* save the current value before cli_realloc() */
tmp_matcher = cli_realloc(matcher->suffix_regexes, (n + 1) * sizeof(*matcher->suffix_regexes));
if (!tmp_matcher) {

@ -1,5 +1,4 @@
cmake_minimum_required( VERSION 3.13 )
# Copyright (C) 2019-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
add_compile_definitions(RARDLL)
add_compile_definitions(WARN_DLOPEN_FAIL)
@ -96,4 +95,4 @@ else()
install(TARGETS clamunrar DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()
add_library( ClamAV::libclamunrar ALIAS clamunrar )
add_library( ClamAV::libunrar ALIAS clamunrar )

@ -1,5 +1,4 @@
cmake_minimum_required( VERSION 3.13 )
# Copyright (C) 2019-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
@ -23,7 +22,7 @@ target_include_directories( clamunrar_iface_iface
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> )
add_library( ClamAV::libclamunrar_iface_iface ALIAS clamunrar_iface_iface )
add_library( ClamAV::libunrar_iface_iface ALIAS clamunrar_iface_iface )
if(ENABLE_UNRAR)
# The clamunrar_iface SHARED library.
@ -38,7 +37,7 @@ if(ENABLE_UNRAR)
target_include_directories( clamunrar_iface
PRIVATE
"${CMAKE_BINARY_DIR}" # For clamav-config.h
$<TARGET_PROPERTY:ClamAV::libclamunrar,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:ClamAV::libunrar,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:ClamAV::libclamav,INTERFACE_INCLUDE_DIRECTORIES> )
set_target_properties( clamunrar_iface PROPERTIES
@ -51,9 +50,9 @@ if(ENABLE_UNRAR)
# Private (internal-only) dependencies.
target_link_libraries( clamunrar_iface
PRIVATE
ClamAV::libclamunrar
ClamAV::libunrar
PUBLIC
ClamAV::libclamunrar_iface_iface)
ClamAV::libunrar_iface_iface)
if(WIN32)
install(TARGETS clamunrar_iface DESTINATION ${CMAKE_INSTALL_PREFIX})
@ -61,5 +60,5 @@ if(ENABLE_UNRAR)
install(TARGETS clamunrar_iface DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()
add_library( ClamAV::libclamunrar_iface ALIAS clamunrar_iface )
add_library( ClamAV::libunrar_iface ALIAS clamunrar_iface )
endif()

@ -1,10 +1,11 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
# Windows compatibility headers
include_directories(${CMAKE_SOURCE_DIR}/win32/compat)
@ -67,6 +68,31 @@ if(ENABLE_SHARED_LIB)
VERSION ${LIBFRESHCLAM_VERSION} SOVERSION ${LIBFRESHCLAM_SOVERSION})
if(WIN32)
install(TARGETS freshclam DESTINATION ${CMAKE_INSTALL_PREFIX})
# Also install shared library (DLL) dependencies
install(CODE [[
file(GET_RUNTIME_DEPENDENCIES
LIBRARIES
$<TARGET_FILE:ClamAV::libfreshclam>
RESOLVED_DEPENDENCIES_VAR _r_deps
UNRESOLVED_DEPENDENCIES_VAR _u_deps
DIRECTORIES
$<TARGET_FILE_DIR:CURL::libcurl>
$<TARGET_FILE_DIR:OpenSSL::SSL>
$<TARGET_FILE_DIR:OpenSSL::Crypto>
)
foreach(_file ${_r_deps})
string(TOLOWER ${_file} _file_lower)
if(NOT ${_file_lower} MATCHES "c:[\\/]windows[\\/]system32.*")
file(INSTALL
DESTINATION "${CMAKE_INSTALL_PREFIX}"
TYPE SHARED_LIBRARY
FOLLOW_SYMLINK_CHAIN
FILES "${_file}"
)
endif()
endforeach()
#message("UNRESOLVED_DEPENDENCIES_VAR: ${_u_deps}")
]])
else()
install(TARGETS freshclam DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()

@ -1,10 +1,11 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
# Windows compatibility headers
include_directories(${CMAKE_SOURCE_DIR}/win32/compat)

@ -1,10 +1,11 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
# Windows compatibility headers
include_directories(${CMAKE_SOURCE_DIR}/win32/compat)

@ -0,0 +1,76 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
#
# Assemble split files found in test/.split
#
set(TESTFILES
clam.cab
clam.exe
clam.zip
clam.arj
clam.exe.rtf
clam.exe.szdd
clam.tar.gz
clam.chm
clam.sis
clam-aspack.exe
clam-pespin.exe
clam-upx.exe
clam-fsg.exe
clam-mew.exe
clam-nsis.exe
clam-petite.exe
clam-upack.exe
clam-wwpack.exe
clam.pdf
clam.mail
clam.ppt
clam.tnef
clam.ea05.exe
clam.ea06.exe
clam.d64.zip
clam.exe.mbox.base64
clam.exe.mbox.uu
clam.exe.binhex
clam.ole.doc
clam.impl.zip
clam.exe.html
clam.bin-be.cpio
clam.bin-le.cpio
clam.newc.cpio
clam.odc.cpio
clam-yc.exe
clam_IScab_int.exe
clam_IScab_ext.exe
clam_ISmsi_int.exe
clam_ISmsi_ext.exe
clam.7z
clam_cache_emax.tgz
clam.iso
clamjol.iso
clam.exe.bz2
clam.bz2.zip
)
if(ENABLE_UNRAR)
set(TESTFILES ${TESTFILES}
clam-v2.rar clam-v3.rar
)
endif()
# Assemble split test file
function(assemble_testfile test_file)
add_custom_command(OUTPUT ${test_file}
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/assemble_testfile.py
${test_file}
--build_dir ${CMAKE_CURRENT_BINARY_DIR}
--split_dir ${CMAKE_CURRENT_SOURCE_DIR}/.split
COMMENT "Assembling test file ${test_file} of ${original}")
add_custom_target(tgt_${test_file} ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${test_file})
#install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${test_file} DESTINATION ${CMAKE_INSTALL_BINDIR})
endfunction()
foreach(TESTFILE ${TESTFILES})
assemble_testfile(${TESTFILE})
endforeach()

@ -0,0 +1,47 @@
#!/usr/bin/env python3
import argparse
import os
from pathlib import Path
def main():
parser = argparse.ArgumentParser()
parser.add_argument("test_file")
parser.add_argument("--split_dir", help="Location of split files", required=True)
parser.add_argument("--build_dir", help="Location to assemble file", required=True)
args = parser.parse_args()
split_dir = Path(args.split_dir)
if not split_dir.exists():
print(f"Error: Split directory does not exist: {args.split_dir}")
match_pattern = Path(split_dir, f"split.{args.test_file}a*")
input_files = [
x for x in split_dir.iterdir() if (x.is_file() and x.match(f"{match_pattern}"))
]
if len(input_files) == 0:
print(f"Error: No splits matching '{args.test_file}' in: {args.split_dir}")
exit(1)
test_file_path = Path(args.build_dir, args.test_file)
try:
test_file_path.touch(0o666, exist_ok=True)
except FileNotFoundError:
print(f"Failed to create file: {test_file_path}")
exit(1)
input_files.sort()
file_data = bytes()
for split_file in input_files:
file_data += split_file.read_bytes()
test_file_path.write_bytes(file_data)
print(f"Assembled: '{test_file_path}'")
if __name__ == "__main__":
main()

@ -0,0 +1,385 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
#
# Assemble additional split files found in unit_tests/.split
#
set(TESTFILES
clam-phish-exe
)
# Assemble split test file
function(assemble_testfile test_file)
add_custom_command(OUTPUT ${test_file}
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/../test/assemble_testfile.py
${test_file}
--build_dir ${CMAKE_CURRENT_BINARY_DIR}
--split_dir ${CMAKE_CURRENT_SOURCE_DIR}/.split
COMMENT "Assembling test file ${test_file} of ${original}")
add_custom_target(tgt_${test_file} ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${test_file})
#install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${test_file} DESTINATION ${CMAKE_INSTALL_BINDIR})
endfunction()
foreach(TESTFILE ${TESTFILES})
assemble_testfile(${TESTFILE})
endforeach()
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
# Windows compatibility headers
include_directories(${CMAKE_SOURCE_DIR}/win32/compat)
endif()
#
# Programs used by tests
#
# preprocessor defines for test programs
if(WIN32)
file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR} OBJDIR)
string(REPLACE "\\" "\\\\" OBJDIR ${OBJDIR})
file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR} BUILDDIR)
string(REPLACE "\\" "\\\\" BUILDDIR ${BUILDDIR})
file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR} SRCDIR)
string(REPLACE "\\" "\\\\" SRCDIR ${SRCDIR})
else()
set(OBJDIR ${CMAKE_CURRENT_BINARY_DIR})
set(BUILDDIR ${CMAKE_CURRENT_BINARY_DIR})
set(SRCDIR ${CMAKE_CURRENT_SOURCE_DIR})
endif()
if(ENABLE_APP)
# check_fpu_endian is used by the clamscan tests
add_executable(check_fpu_endian)
target_sources(check_fpu_endian
PRIVATE
checks.h
check_fpu_endian.c)
target_link_libraries(check_fpu_endian
PRIVATE
libcheck::check
clamav_obj
regex
lzma_sdk
yara
tomsfastmath
bytecode_runtime
ClamAV::libunrar_iface_iface
JSONC::jsonc
ClamAV::libmspack
OpenSSL::SSL
OpenSSL::Crypto
ZLIB::ZLIB
BZip2::BZip2
PCRE2::pcre2
LibXml2::LibXml2)
target_include_directories(check_fpu_endian PRIVATE ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/libclamav ${CMAKE_BINARY_DIR})
target_compile_definitions(check_fpu_endian PUBLIC OBJDIR="${OBJDIR}" BUILDDIR="${BUILDDIR}" SRCDIR="${SRCDIR}")
# check_clamd is used by the clamd tests
add_executable(check_clamd)
target_sources(check_clamd
PRIVATE check_clamd.c checks.h)
target_link_libraries(check_clamd
PRIVATE
libcheck::check
ClamAV::shared
clamav_obj
regex
lzma_sdk
yara
tomsfastmath
bytecode_runtime
ClamAV::libunrar_iface_iface
JSONC::jsonc
ClamAV::libmspack
OpenSSL::SSL
OpenSSL::Crypto
ZLIB::ZLIB
BZip2::BZip2
PCRE2::pcre2
LibXml2::LibXml2)
target_include_directories(check_clamd PRIVATE ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/libclamav ${CMAKE_BINARY_DIR})
target_compile_definitions(check_clamd PUBLIC OBJDIR="${OBJDIR}" BUILDDIR="${BUILDDIR}" SRCDIR="${SRCDIR}")
ADD_CUSTOM_COMMAND(TARGET check_clamd
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/input/clamav.hdb ${CMAKE_CURRENT_BINARY_DIR}/.)
endif()
#
# Test executables
#
add_executable(check_clamav)
target_sources(check_clamav
PRIVATE
checks.h
check_bytecode.c
check_clamav.c
check_disasm.c
check_htmlnorm.c
check_jsnorm.c
check_matchers.c
check_regex.c
check_str.c
check_uniq.c)
target_link_libraries(check_clamav
PRIVATE
libcheck::check
clamav_obj
regex
lzma_sdk
yara
tomsfastmath
bytecode_runtime
ClamAV::libunrar_iface_iface
JSONC::jsonc
ClamAV::libmspack
OpenSSL::SSL
OpenSSL::Crypto
ZLIB::ZLIB
BZip2::BZip2
PCRE2::pcre2
LibXml2::LibXml2)
target_include_directories(check_clamav PRIVATE ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/libclamav ${CMAKE_BINARY_DIR})
target_compile_definitions(check_clamav PUBLIC OBJDIR="${OBJDIR}" BUILDDIR="${BUILDDIR}" SRCDIR="${SRCDIR}")
#
# Paths to pass to our tests via environment variables
#
if(WIN32)
file(TO_NATIVE_PATH ${CMAKE_SOURCE_DIR} SOURCE)
file(TO_NATIVE_PATH ${CMAKE_BINARY_DIR} BUILD)
file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR} TMP)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/check_clamav.exe CHECK_CLAMAV)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/check_clamd.exe CHECK_CLAMD)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/check_fpu_endian.exe CHECK_FPU_ENDIAN)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/clambc.exe CLAMBC)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/clamd.exe CLAMD)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/clamdscan.exe CLAMDSCAN)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/clamdtop.exe CLAMDTOP)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/clamscan.exe CLAMSCAN)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/clamsubmit.exe CLAMSUBMIT)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/clamconf.exe CLAMCONF)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/freshclam.exe FRESHCLAM)
file(TO_NATIVE_PATH $<TARGET_FILE_DIR:check_clamav>/sigtool.exe SIGTOOL)
else()
set(LD_LIBRARY_PATH $<TARGET_FILE_DIR:ClamAV::libclamav>:$<TARGET_FILE_DIR:ClamAV::libmspack:$<TARGET_FILE_DIR:ClamAV::libunrar_iface>:$<TARGET_FILE_DIR:ClamAV::libunrar:$<TARGET_FILE_DIR:ClamAV::libfreshclam>:$ENV{LD_LIBRARY_PATH})
set(SOURCE ${CMAKE_SOURCE_DIR})
set(BUILD ${CMAKE_BINARY_DIR})
set(TMP ${CMAKE_CURRENT_BINARY_DIR})
set(CHECK_CLAMAV $<TARGET_FILE:check_clamav>)
set(CHECK_CLAMD $<TARGET_FILE:check_clamd>)
set(CHECK_FPU_ENDIAN $<TARGET_FILE:check_fpu_endian>)
set(CLAMBC $<TARGET_FILE:clambc>)
set(CLAMD $<TARGET_FILE:clamd>)
set(CLAMDSCAN $<TARGET_FILE:clamdscan>)
set(CLAMDTOP $<TARGET_FILE:clamdtop>)
set(CLAMSCAN $<TARGET_FILE:clamscan>)
set(CLAMSUBMIT $<TARGET_FILE:clamsubmit>)
set(CLAMCONF $<TARGET_FILE:clamconf>)
set(FRESHCLAM $<TARGET_FILE:freshclam-bin>)
set(SIGTOOL $<TARGET_FILE:sigtool>)
set(CLAMAV_MILTER $<TARGET_FILE:clamav-milter>)
set(CLAMONACC $<TARGET_FILE:clamonacc>)
endif()
set(ENVIRONMENT
PYTHONTRACEMALLOC=1 VERSION=${PROJECT_VERSION}${VERSION_SUFFIX}
SOURCE=${SOURCE} BUILD=${BUILD} TMP=${TMP}
CK_FORK=no
CK_DEFAULT_TIMEOUT=60
LD_LIBRARY_PATH=${LD_LIBRARY_PATH}
SOURCE=${SOURCE}
BUILD=${BUILD}
TMP=${TMP}
CHECK_CLAMAV=${CHECK_CLAMAV}
CHECK_CLAMD=${CHECK_CLAMD}
CHECK_FPU_ENDIAN=${CHECK_FPU_ENDIAN}
CLAMBC=${CLAMBC}
CLAMD=${CLAMD}
CLAMDSCAN=${CLAMDSCAN}
CLAMDTOP=${CLAMDTOP}
CLAMSCAN=${CLAMSCAN}
CLAMSUBMIT=${CLAMSUBMIT}
CLAMCONF=${CLAMCONF}
FRESHCLAM=${FRESHCLAM}
SIGTOOL=${SIGTOOL}
CLAMAV_MILTER=${CLAMAV_MILTER}
CLAMONACC=${CLAMONACC}
)
#
# The Tests
# ~~~~~~~~~
#
# Run all tests with: `ctest`
# or: `ctest -V` for verbose output
#
# Run a specific test like this:
# `ctest -V -R libclamav_valgrind_test`
#
add_test(NAME libclamav COMMAND ${Python3_EXECUTABLE} -m;unittest;--verbose;libclamav_test.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TEST libclamav PROPERTY ENVIRONMENT ${ENVIRONMENT})
if(Valgrind_FOUND)
add_test(NAME libclamav_valgrind COMMAND ${Python3_EXECUTABLE} -m;unittest;--verbose;libclamav_test.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TEST libclamav_valgrind PROPERTY ENVIRONMENT ${ENVIRONMENT} VALGRIND=${Valgrind_EXECUTABLE})
endif()
if(ENABLE_APP)
add_test(NAME clamscan COMMAND ${Python3_EXECUTABLE} -m;unittest;--verbose;clamscan_test.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TEST clamscan PROPERTY ENVIRONMENT ${ENVIRONMENT})
if(Valgrind_FOUND)
add_test(NAME clamscan_valgrind COMMAND ${Python3_EXECUTABLE} -m;unittest;--verbose;clamscan_test.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TEST clamscan_valgrind PROPERTY ENVIRONMENT ${ENVIRONMENT} VALGRIND=${Valgrind_EXECUTABLE})
endif()
add_test(NAME clamd COMMAND ${Python3_EXECUTABLE} -m;unittest;--verbose;clamd_test.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TEST clamd PROPERTY ENVIRONMENT ${ENVIRONMENT})
if(Valgrind_FOUND)
add_test(NAME clamd_valgrind COMMAND ${Python3_EXECUTABLE} -m;unittest;--verbose;clamd_test.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TEST clamd_valgrind PROPERTY ENVIRONMENT ${ENVIRONMENT} VALGRIND=${Valgrind_EXECUTABLE})
endif()
add_test(NAME freshclam COMMAND ${Python3_EXECUTABLE} -m;unittest;--verbose;freshclam_test.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TEST freshclam PROPERTY ENVIRONMENT ${ENVIRONMENT})
if(Valgrind_FOUND)
add_test(NAME freshclam_valgrind COMMAND ${Python3_EXECUTABLE} -m;unittest;--verbose;freshclam_test.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TEST freshclam_valgrind PROPERTY ENVIRONMENT ${ENVIRONMENT} VALGRIND=${Valgrind_EXECUTABLE})
endif()
add_test(NAME sigtool COMMAND ${Python3_EXECUTABLE} -m;unittest;--verbose;sigtool_test.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TEST sigtool PROPERTY ENVIRONMENT ${ENVIRONMENT})
if(Valgrind_FOUND)
add_test(NAME sigtool_valgrind COMMAND ${Python3_EXECUTABLE} -m;unittest;--verbose;sigtool_test.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TEST sigtool_valgrind PROPERTY ENVIRONMENT ${ENVIRONMENT} VALGRIND=${Valgrind_EXECUTABLE})
endif()
endif()
if(WIN32)
#
# Prepare a test install, with all our DLL dependencies co-located with our EXEs and DLLs
# Generate GetLibs-$<CONFIG>.ctest which will collect all required DLL and EXE dependencies when `ctest` is run.
#
if(ENABLE_APP)
set(GEN_SCRIPT [[
# Collect runtime DLL dependencies for our libs and apps
file(GET_RUNTIME_DEPENDENCIES
LIBRARIES
$<TARGET_FILE:ClamAV::libclamav>
$<TARGET_FILE:ClamAV::libfreshclam>
EXECUTABLES
$<TARGET_FILE:check_clamav>
$<TARGET_FILE:check_fpu_endian>
$<TARGET_FILE:check_clamd>
$<TARGET_FILE:clambc>
$<TARGET_FILE:clamd>
$<TARGET_FILE:clamdscan>
$<TARGET_FILE:clamdtop>
$<TARGET_FILE:clamscan>
$<TARGET_FILE:clamsubmit>
$<TARGET_FILE:clamconf>
$<TARGET_FILE:freshclam-bin>
$<TARGET_FILE:sigtool>
RESOLVED_DEPENDENCIES_VAR _r_deps
UNRESOLVED_DEPENDENCIES_VAR _u_deps
DIRECTORIES
$<TARGET_FILE_DIR:OpenSSL::SSL>
$<TARGET_FILE_DIR:OpenSSL::Crypto>
$<TARGET_FILE_DIR:ZLIB::ZLIB>
$<TARGET_FILE_DIR:BZip2::BZip2>
$<TARGET_FILE_DIR:PCRE2::pcre2>
$<TARGET_FILE_DIR:LibXml2::LibXml2>
$<TARGET_FILE_DIR:CURL::libcurl>
$<TARGET_FILE_DIR:JSONC::jsonc>
CONFLICTING_DEPENDENCIES_PREFIX CTEST_CONFLICTING_DEPENDENCIES
)
foreach(_file ${_r_deps})
string(TOLOWER ${_file} _file_lower)
if(NOT ${_file_lower} MATCHES "c:[\\/]windows[\\/]system32.*")
message("Collecting DLL dependency: ${_file}")
file(COPY ${_file} DESTINATION $<TARGET_FILE_DIR:check_clamav>)
endif()
endforeach()
# Collect our libs
file(COPY $<TARGET_FILE:ClamAV::libclamav> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:ClamAV::libmspack> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:ClamAV::libfreshclam> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:ClamAV::libunrar> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:ClamAV::libunrar_iface> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
# Collect our apps
file(COPY $<TARGET_FILE:check_fpu_endian> DESTINATION $<TARGET_FILE_DIR:check_fpu_endian>)
file(COPY $<TARGET_FILE:check_clamd> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:clambc> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:clamd> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:clamdscan> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:clamdtop> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:clamscan> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:clamsubmit> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
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>)
]])
else()
# We don't have libfreshclam unit tests, so no need to check if ENABLE_LIBCLAMAV_ONLY is enabled.
set(GEN_SCRIPT [[
# Collect runtime DLL dependencies for our libs
file(GET_RUNTIME_DEPENDENCIES
LIBRARIES
$<TARGET_FILE:ClamAV::libclamav>
EXECUTABLES
$<TARGET_FILE:check_clamav>
RESOLVED_DEPENDENCIES_VAR _r_deps
UNRESOLVED_DEPENDENCIES_VAR _u_deps
DIRECTORIES
$<TARGET_FILE_DIR:OpenSSL::SSL>
$<TARGET_FILE_DIR:OpenSSL::Crypto>
$<TARGET_FILE_DIR:ZLIB::ZLIB>
$<TARGET_FILE_DIR:BZip2::BZip2>
$<TARGET_FILE_DIR:PCRE2::pcre2>
$<TARGET_FILE_DIR:LibXml2::LibXml2>
$<TARGET_FILE_DIR:JSONC::jsonc>
CONFLICTING_DEPENDENCIES_PREFIX CTEST_CONFLICTING_DEPENDENCIES
)
foreach(_file ${_r_deps})
string(TOLOWER ${_file} _file_lower)
if(NOT ${_file_lower} MATCHES "c:[\\/]windows[\\/]system32.*")
message("DEPENDENCY: ${_file}")
file(COPY ${_file} DESTINATION $<TARGET_FILE_DIR:check_clamav>)
endif()
endforeach()
# Collect our libs
file(COPY $<TARGET_FILE:ClamAV::libclamav> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:ClamAV::libmspack> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:ClamAV::libunrar> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
file(COPY $<TARGET_FILE:ClamAV::libunrar_iface> DESTINATION $<TARGET_FILE_DIR:check_clamav>)
]])
endif()
file(GENERATE OUTPUT GetLibs-$<CONFIG>.ctest CONTENT ${GEN_SCRIPT})
set_directory_properties(PROPERTIES TEST_INCLUDE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Run-GetLibs.ctest)
endif()

@ -31,7 +31,7 @@ check_SCRIPTS = $(scripts)
AM_CFLAGS=@WERR_CFLAGS@
if HAVE_LIBCHECK
check_clamav_SOURCES = check_clamav.c checks.h checks_common.h \
check_clamav_SOURCES = check_clamav.c checks.h \
check_jsnorm.c check_str.c check_regex.c \
check_disasm.c check_uniq.c check_matchers.c \
check_htmlnorm.c check_bytecode.c
@ -39,9 +39,9 @@ check_clamav_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/libclamav -I$(top_buildd
check_clamav_LDADD = $(top_builddir)/libclamav/libclamav.la @THREAD_LIBS@ @CHECK_LIBS@
check_clamav_LDFLAGS = $(XML_LIBS)
check_clamav_CFLAGS = $(AM_CFLAGS) @XML_CPPFLAGS@
check_clamd_SOURCES = check_clamd.c checks_common.h
check_clamd_SOURCES = check_clamd.c
check_clamd_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/libclamav -I$(top_builddir)/libclamav -I$(top_srcdir)/shared @CHECK_CPPFLAGS@ @SSL_CPPFLAGS@ @JSON_CPPFLAGS@ @PCRE_CPPFLAGS@ -DSRCDIR=\"$(abs_srcdir)\" -DBUILDDIR=\"$(abs_builddir)\"
check_clamd_LDADD = @CHECK_LIBS@ @CLAMD_LIBS@
check_clamd_LDADD = $(top_builddir)/libclamav/libclamav.la @CHECK_LIBS@ @CLAMD_LIBS@
else
check_clamd_SOURCES = check_clamav_skip.c
check_clamav_SOURCES = check_clamav_skip.c

@ -0,0 +1 @@
include(GetLibs-${CTEST_CONFIGURATION_TYPE}.ctest)

@ -54,7 +54,7 @@ static void runtest(const char *file, uint64_t expected, int fail, int nojit,
{
fmap_t *map = NULL;
int rc;
int fd = open_testfile(file);
int fd = open_testfile(file, O_RDONLY);
FILE *f;
struct cli_bc bc;
cli_ctx cctx;
@ -116,10 +116,10 @@ static void runtest(const char *file, uint64_t expected, int fail, int nojit,
ctx->ctx = &cctx;
if (infile) {
snprintf(filestr, sizeof(filestr), OBJDIR "/%s", infile);
snprintf(filestr, sizeof(filestr), OBJDIR PATHSEP "%s", infile);
fdin = open(filestr, O_RDONLY);
if (fdin < 0 && errno == ENOENT)
fdin = open_testfile(infile);
fdin = open_testfile(infile, O_RDONLY);
ck_assert_msg(fdin >= 0, "failed to open infile");
map = fmap(fdin, 0, 0, filestr);
ck_assert_msg(!!map, "unable to fmap infile");
@ -158,56 +158,56 @@ static void runtest(const char *file, uint64_t expected, int fail, int nojit,
START_TEST(test_retmagic_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/retmagic.cbc", 0x1234f00d, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 0);
runtest("input/retmagic.cbc", 0x1234f00d, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 1);
runtest("input" PATHSEP "retmagic.cbc", 0x1234f00d, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "retmagic.cbc", 0x1234f00d, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 1);
}
END_TEST
START_TEST(test_retmagic_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/retmagic.cbc", 0x1234f00d, CL_SUCCESS, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "retmagic.cbc", 0x1234f00d, CL_SUCCESS, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_arith_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/arith.cbc", 0xd5555555, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "arith.cbc", 0xd5555555, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_arith_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/arith.cbc", 0xd5555555, CL_SUCCESS, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "arith.cbc", 0xd5555555, CL_SUCCESS, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_apicalls_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/apicalls.cbc", 0xf00d, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "apicalls.cbc", 0xf00d, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_apicalls_int)
{
runtest("input/apicalls.cbc", 0xf00d, CL_SUCCESS, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "apicalls.cbc", 0xf00d, CL_SUCCESS, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_apicalls2_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/apicalls2.cbc", 0xf00d, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "apicalls2.cbc", 0xf00d, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_apicalls2_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/apicalls2.cbc", 0xf00d, CL_SUCCESS, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "apicalls2.cbc", 0xf00d, CL_SUCCESS, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
@ -215,41 +215,41 @@ START_TEST(test_div0_jit)
{
cl_init(CL_INIT_DEFAULT);
/* must not crash on div#0 but catch it */
runtest("input/div0.cbc", 0, CL_EBYTECODE, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "div0.cbc", 0, CL_EBYTECODE, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_div0_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/div0.cbc", 0, CL_EBYTECODE, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "div0.cbc", 0, CL_EBYTECODE, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_lsig_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/lsig.cbc", 0, 0, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "lsig.cbc", 0, 0, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_lsig_int)
{
runtest("input/lsig.cbc", 0, 0, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "lsig.cbc", 0, 0, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_inf_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/inf.cbc", 0, CL_ETIMEOUT, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "inf.cbc", 0, CL_ETIMEOUT, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_inf_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/inf.cbc", 0, CL_ETIMEOUT, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "inf.cbc", 0, CL_ETIMEOUT, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
@ -271,7 +271,7 @@ START_TEST(test_matchwithread_jit)
sect.uvsz = 4096;
sect.uraw = 1;
sect.ursz = 512;
runtest("input/matchwithread.cbc", 0, 0, 0, "../test/clam.exe", &pedata,
runtest("input" PATHSEP "matchwithread.cbc", 0, 0, 0, ".." PATHSEP "test" PATHSEP "clam.exe", &pedata,
&sect, "ClamAV-Test-File-detected-via-bytecode", 0);
}
END_TEST
@ -294,7 +294,7 @@ START_TEST(test_matchwithread_int)
sect.uvsz = 4096;
sect.uraw = 1;
sect.ursz = 512;
runtest("input/matchwithread.cbc", 0, 0, 1, "../test/clam.exe", &pedata,
runtest("input" PATHSEP "matchwithread.cbc", 0, 0, 1, ".." PATHSEP "test" PATHSEP "clam.exe", &pedata,
&sect, "ClamAV-Test-File-detected-via-bytecode", 0);
}
END_TEST
@ -302,182 +302,182 @@ END_TEST
START_TEST(test_pdf_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/pdf.cbc", 0, 0, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "pdf.cbc", 0, 0, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_pdf_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/pdf.cbc", 0, 0, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "pdf.cbc", 0, 0, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_bswap_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/bswap.cbc", 0xbeef, 0, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "bswap.cbc", 0xbeef, 0, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_bswap_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/bswap.cbc", 0xbeef, 0, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "bswap.cbc", 0xbeef, 0, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_inflate_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/inflate.cbc", 0xbeef, 0, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "inflate.cbc", 0xbeef, 0, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_inflate_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/inflate.cbc", 0xbeef, 0, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "inflate.cbc", 0xbeef, 0, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_api_extract_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/api_extract_7.cbc", 0xf00d, 0, 0, "input/apitestfile", NULL, NULL, NULL, 0);
runtest("input" PATHSEP "api_extract_7.cbc", 0xf00d, 0, 0, "input" PATHSEP "apitestfile", NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_api_files_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/api_files_7.cbc", 0xf00d, 0, 0, "input/apitestfile", NULL, NULL, NULL, 0);
runtest("input" PATHSEP "api_files_7.cbc", 0xf00d, 0, 0, "input" PATHSEP "apitestfile", NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_apicalls2_7_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/apicalls2_7.cbc", 0xf00d, 0, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "apicalls2_7.cbc", 0xf00d, 0, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_apicalls_7_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/apicalls_7.cbc", 0xf00d, 0, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "apicalls_7.cbc", 0xf00d, 0, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_arith_7_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/arith_7.cbc", 0xd55555dd, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "arith_7.cbc", 0xd55555dd, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_debug_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/debug_7.cbc", 0xf00d, 0, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "debug_7.cbc", 0xf00d, 0, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_inf_7_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/inf_7.cbc", 0, CL_ETIMEOUT, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "inf_7.cbc", 0, CL_ETIMEOUT, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_lsig_7_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/lsig_7.cbc", 0, 0, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "lsig_7.cbc", 0, 0, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_retmagic_7_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/retmagic_7.cbc", 0x1234f00d, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "retmagic_7.cbc", 0x1234f00d, CL_SUCCESS, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_testadt_jit)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/testadt_7.cbc", 0xf00d, 0, 0, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "testadt_7.cbc", 0xf00d, 0, 0, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_api_extract_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/api_extract_7.cbc", 0xf00d, 0, 1, "input/apitestfile", NULL, NULL, NULL, 0);
runtest("input" PATHSEP "api_extract_7.cbc", 0xf00d, 0, 1, "input" PATHSEP "apitestfile", NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_api_files_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/api_files_7.cbc", 0xf00d, 0, 1, "input/apitestfile", NULL, NULL, NULL, 0);
runtest("input" PATHSEP "api_files_7.cbc", 0xf00d, 0, 1, "input" PATHSEP "apitestfile", NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_apicalls2_7_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/apicalls2_7.cbc", 0xf00d, 0, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "apicalls2_7.cbc", 0xf00d, 0, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_apicalls_7_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/apicalls_7.cbc", 0xf00d, 0, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "apicalls_7.cbc", 0xf00d, 0, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_arith_7_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/arith_7.cbc", 0xd55555dd, CL_SUCCESS, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "arith_7.cbc", 0xd55555dd, CL_SUCCESS, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_debug_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/debug_7.cbc", 0xf00d, 0, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "debug_7.cbc", 0xf00d, 0, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_inf_7_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/inf_7.cbc", 0, CL_ETIMEOUT, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "inf_7.cbc", 0, CL_ETIMEOUT, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_lsig_7_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/lsig_7.cbc", 0, 0, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "lsig_7.cbc", 0, 0, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_retmagic_7_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/retmagic_7.cbc", 0x1234f00d, CL_SUCCESS, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "retmagic_7.cbc", 0x1234f00d, CL_SUCCESS, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
START_TEST(test_testadt_int)
{
cl_init(CL_INIT_DEFAULT);
runtest("input/testadt_7.cbc", 0xf00d, 0, 1, NULL, NULL, NULL, NULL, 0);
runtest("input" PATHSEP "testadt_7.cbc", 0xf00d, 0, 1, NULL, NULL, NULL, NULL, 0);
}
END_TEST
@ -493,18 +493,18 @@ static void runload(const char *dbname, struct cl_engine *engine, unsigned signo
}
str = cli_malloc(strlen(dbname) + strlen(srcdir) + 2);
ck_assert_msg(!!str, "cli_malloc");
sprintf(str, "%s/%s", srcdir, dbname);
sprintf(str, "%s" PATHSEP "%s", srcdir, dbname);
rc = cl_load(str, engine, &signo, CL_DB_STDOPT);
ck_assert_msg(rc == CL_SUCCESS, "failed to load %s: %s\n",
dbname, cl_strerror(rc));
str, cl_strerror(rc));
ck_assert_msg(signo == signoexp, "different number of signatures loaded, expected %u, got %u\n",
signoexp, signo);
free(str);
rc = cl_engine_compile(engine);
ck_assert_msg(rc == CL_SUCCESS, "failed to load %s: %s\n",
dbname, cl_strerror(rc));
str, cl_strerror(rc));
}
START_TEST(test_load_bytecode_jit)
@ -514,7 +514,7 @@ START_TEST(test_load_bytecode_jit)
engine = cl_engine_new();
ck_assert_msg(!!engine, "failed to create engine\n");
runload("input/bytecode.cvd", engine, 5);
runload("input" PATHSEP "bytecode.cvd", engine, 5);
cl_engine_free(engine);
}
@ -528,7 +528,7 @@ START_TEST(test_load_bytecode_int)
engine->dconf->bytecode = BYTECODE_INTERPRETER;
ck_assert_msg(!!engine, "failed to create engine\n");
runload("input/bytecode.cvd", engine, 5);
runload("input" PATHSEP "bytecode.cvd", engine, 5);
cl_engine_free(engine);
}
@ -548,7 +548,7 @@ static void *thread(void *arg)
/* run all cl_load at once, to maximize chance of a crash
* in case of a race condition */
pthread_barrier_wait(&barrier);
runload("input/bytecode.cvd", engine, 5);
runload("input" PATHSEP "bytecode.cvd", engine, 5);
cl_engine_free(engine);
return NULL;
}

@ -11,7 +11,9 @@
#include <check.h>
#include <sys/types.h>
#include <dirent.h>
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#if HAVE_LIBXML2
#include <libxml/parser.h>
@ -71,12 +73,14 @@ START_TEST(test_cl_debug)
}
END_TEST
#ifndef _WIN32
/* extern const char *cl_retdbdir(void); */
START_TEST(test_cl_retdbdir)
{
ck_assert_msg(!strcmp(DATADIR, cl_retdbdir()), "cl_retdbdir");
}
END_TEST
#endif
#ifndef REPO_VERSION
#define REPO_VERSION VERSION
@ -149,7 +153,7 @@ END_TEST
START_TEST(test_cl_cvdhead)
{
// ck_assert_msg(NULL == cl_cvdhead(NULL), "cl_cvdhead(null)");
// ck_assert_msg(NULL == cl_cvdhead("input/cl_cvdhead/1.txt"), "cl_cvdhead(515 byte file, all nulls)");
// ck_assert_msg(NULL == cl_cvdhead("input" PATHSEP "cl_cvdhead" PATHSEP "1.txt"), "cl_cvdhead(515 byte file, all nulls)");
/* the data read from the file is passed to cl_cvdparse, test cases for that are separate */
}
END_TEST
@ -452,7 +456,7 @@ static void init_testfiles(void)
unsigned i = 0;
int expect = expected_testfiles;
DIR *d = opendir(OBJDIR "/../test");
DIR *d = opendir(OBJDIR PATHSEP ".." PATHSEP "test");
ck_assert_msg(!!d, "opendir");
if (!d)
return;
@ -491,7 +495,7 @@ static int inited = 0;
static void engine_setup(void)
{
unsigned int sigs = 0;
const char *hdb = OBJDIR "/clamav.hdb";
const char *hdb = OBJDIR PATHSEP "clamav.hdb";
init_testfiles();
if (!inited)
@ -516,7 +520,7 @@ static int get_test_file(int i, char *file, unsigned fsize, unsigned long *size)
STATBUF st;
ck_assert_msg(i < testfiles_n, "%i < %i %s", i, testfiles_n, file);
snprintf(file, fsize, OBJDIR "/../test/%s", testfiles[i]);
snprintf(file, fsize, OBJDIR PATHSEP ".." PATHSEP "test" PATHSEP "%s", testfiles[i]);
fd = open(file, O_RDONLY);
ck_assert_msg(fd > 0, "open");
@ -525,6 +529,7 @@ static int get_test_file(int i, char *file, unsigned fsize, unsigned long *size)
return fd;
}
#ifndef _WIN32
static off_t pread_cb(void *handle, void *buf, size_t count, off_t offset)
{
return pread(*((int *)handle), buf, count, offset);
@ -561,6 +566,115 @@ START_TEST(test_cl_scanmap_callback_handle)
}
END_TEST
START_TEST(test_cl_scanmap_callback_handle_allscan)
{
const char *virname = NULL;
unsigned long int scanned = 0;
cl_fmap_t *map;
int ret;
char file[256];
unsigned long size;
struct cl_scan_options options;
memset(&options, 0, sizeof(struct cl_scan_options));
options.parse |= ~0;
options.general |= CL_SCAN_GENERAL_ALLMATCHES;
int fd = get_test_file(_i, file, sizeof(file), &size);
/* intentionally use different way than scanners.c for testing */
map = cl_fmap_open_handle(&fd, 0, size, pread_cb, 1);
ck_assert_msg(!!map, "cl_fmap_open_handle %s");
cli_dbgmsg("scanning (handle) allscan %s\n", file);
ret = cl_scanmap_callback(map, file, &virname, &scanned, g_engine, &options, NULL);
cli_dbgmsg("scan end (handle) allscan %s\n", file);
if (!FALSE_NEGATIVE) {
ck_assert_msg(ret == CL_VIRUS, "cl_scanmap_callback allscan failed for %s: %s", file, cl_strerror(ret));
ck_assert_msg(virname && !strcmp(virname, "ClamAV-Test-File.UNOFFICIAL"), "virusname: %s", virname);
}
cl_fmap_close(map);
close(fd);
}
END_TEST
#endif
#ifdef HAVE_SYS_MMAN_H
START_TEST(test_cl_scanmap_callback_mem)
{
const char *virname = NULL;
unsigned long int scanned = 0;
cl_fmap_t *map;
int ret;
void *mem;
unsigned long size;
char file[256];
struct cl_scan_options options;
memset(&options, 0, sizeof(struct cl_scan_options));
options.parse |= ~0;
int fd = get_test_file(_i, file, sizeof(file), &size);
mem = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
ck_assert_msg(mem != MAP_FAILED, "mmap");
/* intentionally use different way than scanners.c for testing */
map = cl_fmap_open_memory(mem, size);
ck_assert_msg(!!map, "cl_fmap_open_mem");
cli_dbgmsg("scanning (mem) %s\n", file);
ret = cl_scanmap_callback(map, file, &virname, &scanned, g_engine, &options, NULL);
cli_dbgmsg("scan end (mem) %s\n", file);
if (!FALSE_NEGATIVE) {
ck_assert_msg(ret == CL_VIRUS, "cl_scanmap_callback failed for %s: %s", file, cl_strerror(ret));
ck_assert_msg(virname && !strcmp(virname, "ClamAV-Test-File.UNOFFICIAL"), "virusname: %s for %s", virname, file);
}
close(fd);
cl_fmap_close(map);
munmap(mem, size);
}
END_TEST
START_TEST(test_cl_scanmap_callback_mem_allscan)
{
const char *virname = NULL;
unsigned long int scanned = 0;
cl_fmap_t *map;
int ret;
void *mem;
unsigned long size;
char file[256];
struct cl_scan_options options;
memset(&options, 0, sizeof(struct cl_scan_options));
options.parse |= ~0;
options.general |= CL_SCAN_GENERAL_ALLMATCHES;
int fd = get_test_file(_i, file, sizeof(file), &size);
mem = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
ck_assert_msg(mem != MAP_FAILED, "mmap");
/* intentionally use different way than scanners.c for testing */
map = cl_fmap_open_memory(mem, size);
ck_assert_msg(!!map, "cl_fmap_open_mem %s");
cli_dbgmsg("scanning (mem) allscan %s\n", file);
ret = cl_scanmap_callback(map, file, &virname, &scanned, g_engine, &options, NULL);
cli_dbgmsg("scan end (mem) allscan %s\n", file);
if (!FALSE_NEGATIVE) {
ck_assert_msg(ret == CL_VIRUS, "cl_scanmap_callback allscan failed for %s: %s", file, cl_strerror(ret));
ck_assert_msg(virname && !strcmp(virname, "ClamAV-Test-File.UNOFFICIAL"), "virusname: %s for %s", virname, file);
}
close(fd);
cl_fmap_close(map);
munmap(mem, size);
}
END_TEST
#endif
START_TEST(test_fmap_duplicate)
{
cl_fmap_t *map;
@ -843,112 +957,6 @@ START_TEST(test_fmap_duplicate_out_of_bounds)
}
END_TEST
START_TEST(test_cl_scanmap_callback_handle_allscan)
{
const char *virname = NULL;
unsigned long int scanned = 0;
cl_fmap_t *map;
int ret;
char file[256];
unsigned long size;
struct cl_scan_options options;
memset(&options, 0, sizeof(struct cl_scan_options));
options.parse |= ~0;
options.general |= CL_SCAN_GENERAL_ALLMATCHES;
int fd = get_test_file(_i, file, sizeof(file), &size);
/* intentionally use different way than scanners.c for testing */
map = cl_fmap_open_handle(&fd, 0, size, pread_cb, 1);
ck_assert_msg(!!map, "cl_fmap_open_handle %s");
cli_dbgmsg("scanning (handle) allscan %s\n", file);
ret = cl_scanmap_callback(map, file, &virname, &scanned, g_engine, &options, NULL);
cli_dbgmsg("scan end (handle) allscan %s\n", file);
if (!FALSE_NEGATIVE) {
ck_assert_msg(ret == CL_VIRUS, "cl_scanmap_callback allscan failed for %s: %s", file, cl_strerror(ret));
ck_assert_msg(virname && !strcmp(virname, "ClamAV-Test-File.UNOFFICIAL"), "virusname: %s", virname);
}
cl_fmap_close(map);
close(fd);
}
END_TEST
START_TEST(test_cl_scanmap_callback_mem)
{
const char *virname = NULL;
unsigned long int scanned = 0;
cl_fmap_t *map;
int ret;
void *mem;
unsigned long size;
char file[256];
struct cl_scan_options options;
memset(&options, 0, sizeof(struct cl_scan_options));
options.parse |= ~0;
int fd = get_test_file(_i, file, sizeof(file), &size);
mem = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
ck_assert_msg(mem != MAP_FAILED, "mmap");
/* intentionally use different way than scanners.c for testing */
map = cl_fmap_open_memory(mem, size);
ck_assert_msg(!!map, "cl_fmap_open_mem");
cli_dbgmsg("scanning (mem) %s\n", file);
ret = cl_scanmap_callback(map, file, &virname, &scanned, g_engine, &options, NULL);
cli_dbgmsg("scan end (mem) %s\n", file);
if (!FALSE_NEGATIVE) {
ck_assert_msg(ret == CL_VIRUS, "cl_scanmap_callback failed for %s: %s", file, cl_strerror(ret));
ck_assert_msg(virname && !strcmp(virname, "ClamAV-Test-File.UNOFFICIAL"), "virusname: %s for %s", virname, file);
}
close(fd);
cl_fmap_close(map);
munmap(mem, size);
}
END_TEST
START_TEST(test_cl_scanmap_callback_mem_allscan)
{
const char *virname = NULL;
unsigned long int scanned = 0;
cl_fmap_t *map;
int ret;
void *mem;
unsigned long size;
char file[256];
struct cl_scan_options options;
memset(&options, 0, sizeof(struct cl_scan_options));
options.parse |= ~0;
options.general |= CL_SCAN_GENERAL_ALLMATCHES;
int fd = get_test_file(_i, file, sizeof(file), &size);
mem = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
ck_assert_msg(mem != MAP_FAILED, "mmap");
/* intentionally use different way than scanners.c for testing */
map = cl_fmap_open_memory(mem, size);
ck_assert_msg(!!map, "cl_fmap_open_mem %s");
cli_dbgmsg("scanning (mem) allscan %s\n", file);
ret = cl_scanmap_callback(map, file, &virname, &scanned, g_engine, &options, NULL);
cli_dbgmsg("scan end (mem) allscan %s\n", file);
if (!FALSE_NEGATIVE) {
ck_assert_msg(ret == CL_VIRUS, "cl_scanmap_callback allscan failed for %s: %s", file, cl_strerror(ret));
ck_assert_msg(virname && !strcmp(virname, "ClamAV-Test-File.UNOFFICIAL"), "virusname: %s for %s", virname, file);
}
close(fd);
cl_fmap_close(map);
munmap(mem, size);
}
END_TEST
static Suite *test_cl_suite(void)
{
Suite *s = suite_create("cl_suite");
@ -960,7 +968,9 @@ static Suite *test_cl_suite(void)
tcase_add_test(tc_cl, test_cl_free);
tcase_add_test(tc_cl, test_cl_build);
tcase_add_test(tc_cl, test_cl_debug);
#ifndef _WIN32
tcase_add_test(tc_cl, test_cl_retdbdir);
#endif
tcase_add_test(tc_cl, test_cl_retver);
tcase_add_test(tc_cl, test_cl_cvdfree);
tcase_add_test(tc_cl, test_cl_statfree);
@ -988,10 +998,14 @@ static Suite *test_cl_suite(void)
tcase_add_loop_test(tc_cl_scan, test_cl_scandesc_callback_allscan, 0, expect);
tcase_add_loop_test(tc_cl_scan, test_cl_scanfile_callback, 0, expect);
tcase_add_loop_test(tc_cl_scan, test_cl_scanfile_callback_allscan, 0, expect);
#ifndef _WIN32
tcase_add_loop_test(tc_cl_scan, test_cl_scanmap_callback_handle, 0, expect);
tcase_add_loop_test(tc_cl_scan, test_cl_scanmap_callback_handle_allscan, 0, expect);
#endif
#ifdef HAVE_SYS_MMAN_H
tcase_add_loop_test(tc_cl_scan, test_cl_scanmap_callback_mem, 0, expect);
tcase_add_loop_test(tc_cl_scan, test_cl_scanmap_callback_mem_allscan, 0, expect);
#endif
tcase_add_loop_test(tc_cl_scan, test_fmap_duplicate, 0, expect);
tcase_add_loop_test(tc_cl_scan, test_fmap_duplicate_out_of_bounds, 0, expect);
@ -1316,23 +1330,15 @@ START_TEST(test_sanitize_path)
ck_assert_msg(!strcmp(expected_base, sanitized_base), "Expected: \"%s\", Found: \"%s\"", expected_base, sanitized_base);
free(sanitized);
unsanitized = "relative/../../bad_win_posix_path";
#ifdef _WIN32
expected = "relative\\..\\bad_win_posix_path";
#else
expected = "relative/../bad_win_posix_path";
#endif
sanitized = cli_sanitize_filepath(unsanitized, strlen(unsanitized), NULL);
unsanitized = "relative/../../bad_win_posix_path"; // <-- posix paths intentionally specified -- should still work on Windows)
expected = "relative" PATHSEP ".." PATHSEP "bad_win_posix_path";
sanitized = cli_sanitize_filepath(unsanitized, strlen(unsanitized), NULL);
ck_assert(NULL != sanitized);
ck_assert_msg(!strcmp(expected, sanitized), "Expected: \"%s\", Found: \"%s\"", expected, sanitized);
free(sanitized);
unsanitized = "relative/../../bad_win_posix_path";
#ifdef _WIN32
expected = "relative\\..\\bad_win_posix_path";
#else
expected = "relative/../bad_win_posix_path";
#endif
unsanitized = "relative/../../bad_win_posix_path"; // <-- posix paths intentionally specified -- should still work on Windows)
expected = "relative" PATHSEP ".." PATHSEP "bad_win_posix_path";
expected_base = "bad_win_posix_path";
sanitized = cli_sanitize_filepath(unsanitized, strlen(unsanitized), &sanitized_base);
ck_assert(NULL != sanitized);
@ -1444,7 +1450,7 @@ START_TEST(test_sanitize_path)
}
END_TEST
START_TEST(test_cli_codepage_to_utf8)
START_TEST(test_cli_codepage_to_utf8_jis)
{
cl_error_t ret;
char *utf8 = NULL;
@ -1459,16 +1465,32 @@ START_TEST(test_cli_codepage_to_utf8)
free(utf8);
utf8 = NULL;
}
}
END_TEST
START_TEST(test_cli_codepage_to_utf8_utf16be_null_term)
{
cl_error_t ret;
char *utf8 = NULL;
size_t utf8_size = 0;
ret = cli_codepage_to_utf8("\x00\x48\x00\x65\x00\x6c\x00\x6c\x00\x6f\x00\x20\x00\x77\x00\x6f\x00\x72\x00\x6c\x00\x64\x00\x21\x00\x00", 26, CODEPAGE_UTF16_BE, &utf8, &utf8_size);
ck_assert_msg(CL_SUCCESS == ret, "test_cli_codepage_to_utf8: Failed to convert CODEPAGE_UTF16_LE to UTF8: ret != SUCCESS!");
ck_assert_msg(NULL != utf8, "sanitize_path: Failed to convert CODEPAGE_UTF16_LE to UTF8: utf8 pointer is NULL!");
ck_assert_msg(CL_SUCCESS == ret, "test_cli_codepage_to_utf8: Failed to convert CODEPAGE_UTF16_BE to UTF8: ret != SUCCESS!");
ck_assert_msg(NULL != utf8, "sanitize_path: Failed to convert CODEPAGE_UTF16_BE to UTF8: utf8 pointer is NULL!");
ck_assert_msg(0 == strcmp(utf8, "Hello world!"), "sanitize_path: '%s' doesn't match '%s'", utf8, "Hello world!");
if (NULL != utf8) {
free(utf8);
utf8 = NULL;
}
}
END_TEST
START_TEST(test_cli_codepage_to_utf8_utf16be_no_null_term)
{
cl_error_t ret;
char *utf8 = NULL;
size_t utf8_size = 0;
ret = cli_codepage_to_utf8("\x00\x48\x00\x65\x00\x6c\x00\x6c\x00\x6f\x00\x20\x00\x77\x00\x6f\x00\x72\x00\x6c\x00\x64\x00\x21", 24, CODEPAGE_UTF16_BE, &utf8, &utf8_size);
ck_assert_msg(CL_SUCCESS == ret, "test_cli_codepage_to_utf8: Failed to convert CODEPAGE_UTF16_BE to UTF8: ret != SUCCESS!");
@ -1479,6 +1501,14 @@ START_TEST(test_cli_codepage_to_utf8)
free(utf8);
utf8 = NULL;
}
}
END_TEST
START_TEST(test_cli_codepage_to_utf8_utf16le)
{
cl_error_t ret;
char *utf8 = NULL;
size_t utf8_size = 0;
ret = cli_codepage_to_utf8("\x48\x00\x65\x00\x6c\x00\x6c\x00\x6f\x00\x20\x00\x77\x00\x6f\x00\x72\x00\x6c\x00\x64\x00\x21\x00\x00\x00", 26, CODEPAGE_UTF16_LE, &utf8, &utf8_size);
ck_assert_msg(CL_SUCCESS == ret, "test_cli_codepage_to_utf8: Failed to convert CODEPAGE_UTF16_LE to UTF8: ret != SUCCESS!");
@ -1511,7 +1541,10 @@ static Suite *test_cli_suite(void)
suite_add_tcase(s, tc_cli_assorted);
tcase_add_test(tc_cli_assorted, test_sanitize_path);
tcase_add_test(tc_cli_assorted, test_cli_codepage_to_utf8);
tcase_add_test(tc_cli_assorted, test_cli_codepage_to_utf8_jis);
tcase_add_test(tc_cli_assorted, test_cli_codepage_to_utf8_utf16be_null_term);
tcase_add_test(tc_cli_assorted, test_cli_codepage_to_utf8_utf16be_no_null_term);
tcase_add_test(tc_cli_assorted, test_cli_codepage_to_utf8_utf16le);
return s;
}
@ -1521,7 +1554,7 @@ void errmsg_expected(void)
fputs("cli_errmsg() expected here\n", stderr);
}
int open_testfile(const char *name)
int open_testfile(const char *name, int flags)
{
int fd;
const char *srcdir = getenv("srcdir");
@ -1534,9 +1567,9 @@ int open_testfile(const char *name)
str = cli_malloc(strlen(name) + strlen(srcdir) + 2);
ck_assert_msg(!!str, "cli_malloc");
sprintf(str, "%s/%s", srcdir, name);
sprintf(str, "%s" PATHSEP "%s", srcdir, name);
fd = open(str, O_RDONLY);
fd = open(str, flags);
ck_assert_msg(fd >= 0, "open() failed: %s", str);
free(str);
return fd;
@ -1614,6 +1647,7 @@ void dconf_teardown(void)
#endif
}
#ifndef _WIN32
static void check_version_compatible()
{
/* check 0.9.8 is not ABI compatible with 0.9.6,
@ -1635,6 +1669,7 @@ static void check_version_compatible()
exit(EXIT_FAILURE);
}
}
#endif
int main(void)
{
@ -1646,7 +1681,9 @@ int main(void)
fpu_words = get_fpu_endian();
#ifndef _WIN32
check_version_compatible();
#endif
s = test_cl_suite();
sr = srunner_create(s);
@ -1660,8 +1697,8 @@ int main(void)
srunner_add_suite(sr, test_htmlnorm_suite());
srunner_add_suite(sr, test_bytecode_suite());
srunner_set_log(sr, "test.log");
if (freopen("test-stderr.log", "w+", stderr) == NULL) {
srunner_set_log(sr, OBJDIR PATHSEP "test.log");
if (freopen(OBJDIR PATHSEP "test-stderr.log", "w+", stderr) == NULL) {
fputs("Unable to redirect stderr!\n", stderr);
}
cl_debug();

@ -23,8 +23,10 @@
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#ifndef _WIN32
#include <arpa/inet.h>
#include <netinet/in.h>
#endif
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
@ -34,31 +36,52 @@
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef _WIN32
#include <windows.h>
#include <winsock2.h>
#else
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#endif
#include <sys/stat.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <check.h>
// libclamav
#include "clamav.h"
#include "platform.h"
#include "version.h"
#include "str.h"
// shared
#include "fdpassing.h"
#include "checks_common.h"
static int conn_tcp(int port)
{
struct sockaddr_in server;
int rc;
int sd = socket(AF_INET, SOCK_STREAM, 0);
ck_assert_msg(sd != -1, "Unable to create socket: %s\n", strerror(errno));
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr("127.0.0.1");
rc = connect(sd, (struct sockaddr *)&server, (socklen_t)sizeof(server));
ck_assert_msg(rc != -1, "Unable to connect(): %s\n", strerror(errno));
return sd;
}
static int sockd;
#ifndef _WIN32
#define SOCKET "clamd-test.socket"
static void conn_setup_mayfail(int may)
{
@ -66,7 +89,7 @@ static void conn_setup_mayfail(int may)
struct sockaddr_un nixsock;
memset((void *)&nixsock, 0, sizeof(nixsock));
nixsock.sun_family = AF_UNIX;
strncpy(nixsock.sun_path, SOCKET, sizeof(nixsock.sun_path));
strncpy(nixsock.sun_path, BUILDDIR PATHSEP SOCKET, sizeof(nixsock.sun_path));
sockd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockd == -1 && (may && (errno == EMFILE || errno == ENFILE)))
@ -78,65 +101,76 @@ static void conn_setup_mayfail(int may)
signal(SIGPIPE, SIG_IGN);
}
static void conn_setup(void)
#else
#define PORT 3319
static void conn_setup_mayfail(int may)
{
conn_setup_mayfail(0);
sockd = conn_tcp(PORT);
if (sockd == -1 && (may && (errno == ECONNREFUSED)))
return;
ck_assert_msg(sockd != -1, "Unable to connect(): %s\n", strerror(errno));
}
#endif
static int conn_tcp(int port)
static void conn_setup(void)
{
struct sockaddr_in server;
int rc;
int sd = socket(AF_INET, SOCK_STREAM, 0);
ck_assert_msg(sd != -1, "Unable to create socket: %s\n", strerror(errno));
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr("127.0.0.1");
rc = connect(sd, (struct sockaddr *)&server, (socklen_t)sizeof(server));
ck_assert_msg(rc != -1, "Unable to connect(): %s\n", strerror(errno));
return sd;
conn_setup_mayfail(0);
}
static void conn_teardown(void)
{
if (sockd != -1)
#ifndef _WIN32
close(sockd);
#else
closesocket(sockd);
#endif
}
#ifndef REPO_VERSION
#define REPO_VERSION VERSION
#endif
#define SCANFILE BUILDDIR "/../test/clam.exe"
#define FOUNDREPLY SCANFILE ": ClamAV-Test-File.UNOFFICIAL FOUND"
#define SCANFILE BUILDDIR PATHSEP ".." PATHSEP "test" PATHSEP "clam.exe"
#define FOUNDREPLY "clam.exe: ClamAV-Test-File.UNOFFICIAL FOUND"
/* some clean file */
#define CLEANFILE SRCDIR "/Makefile.am"
#define CLEANFILE SRCDIR PATHSEP "Makefile.am"
#define CLEANREPLY CLEANFILE ": OK"
#define UNKNOWN_REPLY "UNKNOWN COMMAND"
#define NONEXISTENT "/nonexistent\vfilename"
#define NONEXISTENT PATHSEP "nonexistent\vfilename"
#define NONEXISTENT_REPLY NONEXISTENT ": lstat() failed: No such file or directory. ERROR"
#define ACCDENIED BUILDDIR "/accdenied"
#ifndef _WIN32
#define ACCDENIED BUILDDIR PATHSEP "accdenied"
#define ACCDENIED_REPLY ACCDENIED ": Access denied. ERROR"
#endif
static int isroot = 0;
static void commands_setup(void)
{
#ifndef _WIN32
const char *nonempty = "NONEMPTYFILE";
int fd = open(NONEXISTENT, O_RDONLY);
#endif
/*
* Verify that our NONEXISTENT filepath indeed does not exist.
*/
int fd = open(NONEXISTENT, O_RDONLY);
if (fd != -1) close(fd);
ck_assert_msg(fd == -1, "Nonexistent file exists!\n");
#ifndef _WIN32
/*
* Prepare a file path that is write-only.
* Note: doesn't work on Windows (O_RWONLY is implicitly readable), so we skip this test on Windows.
*/
fd = open(ACCDENIED, O_CREAT | O_WRONLY, S_IWUSR);
ck_assert_msg(fd != -1,
"Failed to create file for access denied tests: %s\n", strerror(errno));
ck_assert_msg(fchmod(fd, S_IWUSR) != -1,
"Failed to chmod: %s\n", strerror(errno));
/* must not be empty file */
@ -144,10 +178,12 @@ static void commands_setup(void)
"Failed to write into testfile: %s\n", strerror(errno));
close(fd);
/* skip access denied tests when run as root, as root will ignore
* permissions */
if (!geteuid())
/* Prepare the "isroot" global so we can skip the access-denied tests when run as root
because... you know, root will ignore permissions and still read the file. */
if (!geteuid()) {
isroot = 1;
}
#endif
}
static void commands_teardown(void)
@ -192,10 +228,12 @@ static struct basic_test {
{"SCAN " NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0, IDS_OK},
{"CONTSCAN " NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0, IDS_REJECT},
{"MULTISCAN " NONEXISTENT, NULL, NONEXISTENT_REPLY, 1, 0, IDS_REJECT},
/* commands for access denied files */
/* commands for access denied files */
#ifndef _WIN32
{"SCAN " ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1, IDS_OK},
{"CONTSCAN " ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1, IDS_REJECT},
{"MULTISCAN " ACCDENIED, NULL, ACCDENIED_REPLY, 1, 1, IDS_REJECT},
#endif
/* commands with invalid/missing arguments */
{"SCAN", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
{"CONTSCAN", NULL, UNKNOWN_REPLY, 1, 0, IDS_REJECT},
@ -236,6 +274,7 @@ static void test_command(const char *cmd, size_t len, const char *extra, const c
{
void *recvdata;
ssize_t rc;
char *expected_string_offset = NULL;
rc = send(sockd, cmd, len, 0);
ck_assert_msg((size_t)rc == len, "Unable to send(): %s\n", strerror(errno));
@ -244,14 +283,19 @@ static void test_command(const char *cmd, size_t len, const char *extra, const c
rc = send(sockd, extra, strlen(extra), 0);
ck_assert_msg((size_t)rc == strlen(extra), "Unable to send() extra for %s: %s\n", cmd, strerror(errno));
}
#ifdef _WIN32
shutdown(sockd, SD_SEND);
#else
shutdown(sockd, SHUT_WR);
#endif
recvdata = recvfull(sockd, &len);
ck_assert_msg(len == expect_len, "Reply has wrong size: %lu, expected %lu, reply: %s, expected: %s\n",
len, expect_len, recvdata, expect);
rc = memcmp(recvdata, expect, expect_len);
ck_assert_msg(!rc, "Wrong reply for command %s: |%s|, expected: |%s|\n", cmd, recvdata, expect);
// The path which comes back may be an absolute real path, not a relative path with symlinks ...
// ... so this length check isn't really meaningful anymore.
// For the same reasons, we can't expect the path to match exactly, so we'll
// just make sure expect is found in recvdata and use the basename instead of the full path.
expected_string_offset = CLI_STRNSTR(recvdata, expect, len);
ck_assert_msg(expected_string_offset != NULL, "Wrong reply for command %s: |%s|, expected: |%s|\n", cmd, recvdata, expect);
free(recvdata);
}
@ -353,7 +397,7 @@ static size_t prepare_instream(char *buf, size_t off, size_t buflen)
uint32_t chunk;
ck_assert_msg(CLAMSTAT(SCANFILE, &stbuf) != -1, "stat failed for %s: %s", SCANFILE, strerror(errno));
fd = open(SCANFILE, O_RDONLY);
fd = open(SCANFILE, O_RDONLY | O_BINARY);
ck_assert_msg(fd != -1, "open failed: %s\n", strerror(errno));
chunk = htonl(stbuf.st_size);
@ -397,6 +441,7 @@ START_TEST(test_instream)
}
END_TEST
#ifndef _WIN32
static int sendmsg_fd(int sockd, const char *mesg, size_t msg_len, int fd, int singlemsg)
{
struct msghdr msg;
@ -585,6 +630,7 @@ START_TEST(test_fildes_unwanted)
conn_teardown();
}
END_TEST
#endif
START_TEST(test_idsession_stress)
{
@ -620,46 +666,55 @@ END_TEST
#define TIMEOUT_REPLY "TIMED OUT WAITING FOR COMMAND\n"
#ifndef _WIN32
/*
* Test that we can still interact with clamd when it has a lot of active connections.
*
* Porting this test to work on Windows is too tedious at present.
* I suspect it should be rewritten using threads. For now, skip on Windows.
*/
START_TEST(test_connections)
{
int rc;
int i;
struct rlimit rlim;
int *sock;
int nf, maxfd = 0;
int num_fds, maxfd = 0;
ck_assert_msg(getrlimit(RLIMIT_NOFILE, &rlim) != -1,
"Failed to get RLIMIT_NOFILE: %s\n", strerror(errno));
nf = rlim.rlim_cur - 5;
sock = malloc(sizeof(int) * nf);
num_fds = rlim.rlim_cur - 5;
sock = malloc(sizeof(int) * num_fds);
ck_assert_msg(!!sock, "malloc failed\n");
for (i = 0; i < nf; i++) {
for (i = 0; i < num_fds; i++) {
/* just open connections, and let them time out */
conn_setup_mayfail(1);
if (sockd == -1) {
nf = i;
num_fds = i;
break;
}
sock[i] = sockd;
if (sockd > maxfd)
maxfd = sockd;
}
rc = fork();
ck_assert_msg(rc != -1, "fork() failed: %s\n", strerror(errno));
if (rc == 0) {
/* Child */
char dummy;
int ret;
fd_set rfds;
FD_ZERO(&rfds);
for (i = 0; i < nf; i++) {
for (i = 0; i < num_fds; i++) {
FD_SET(sock[i], &rfds);
}
while (1) {
ret = select(maxfd + 1, &rfds, NULL, NULL, NULL);
if (ret < 0)
break;
for (i = 0; i < nf; i++) {
for (i = 0; i < num_fds; i++) {
if (FD_ISSET(sock[i], &rfds)) {
if (recv(sock[i], &dummy, 1, 0) == 0) {
close(sock[i]);
@ -671,7 +726,8 @@ START_TEST(test_connections)
free(sock);
exit(0);
} else {
for (i = 0; i < nf; i++) {
/* Parent */
for (i = 0; i < num_fds; i++) {
close(sock[i]);
}
free(sock);
@ -681,10 +737,20 @@ START_TEST(test_connections)
test_command("RELOAD", sizeof("RELOAD") - 1, NULL, "RELOADING\n", sizeof("RELOADING\n") - 1);
conn_teardown();
}
/* Ok we're done, kill the child process if it's still up, else it might hang the test framework */
kill(rc, SIGKILL);
}
}
END_TEST
#endif
/*
* Test a stream scan, where the initial response to zSTREAM is a port #
* that clamd will listen to receive the file stream.
* Note: Disabled on Windows because of issues getting the test to work,
* and because zSTREAM was deprecated in 02/2009 in favor of zINSTREAM.
*/
#ifndef _WIN32
START_TEST(test_stream)
{
char buf[BUFSIZ];
@ -700,31 +766,52 @@ START_TEST(test_stream)
ck_assert_msg(
send(sockd, "zSTREAM", sizeof("zSTREAM"), 0) == sizeof("zSTREAM"),
"send failed: %s\n", strerror(errno));
/*
* Receive a port number over which to send the stream.
*/
recvdata = recvpartial(sockd, &len, 1);
ck_assert_msg(sscanf(recvdata, "PORT %u\n", &port) == 1,
"Wrong stream reply: %s\n", recvdata);
free(recvdata);
/*
* Connect & send the file to be scanned.
*/
streamsd = conn_tcp(port);
do {
nread = read(infd, buf, sizeof(buf));
if (nread > 0)
ck_assert_msg(send(streamsd, buf, nread, 0) == nread,
ck_assert_msg(send(sockd, buf, nread, 0) == nread,
"send failed: %s\n", strerror(errno));
} while (nread > 0 || (nread == -1 && errno == EINTR));
ck_assert_msg(nread != -1, "read failed: %s\n", strerror(errno));
close(infd);
ck_assert_msg(nread != -1, "read failed: %s\n", strerror(errno));
/*
* Close the socket, so clamd knows we're done sending and will scan the data.
*/
#ifdef _WIN32
closesocket(streamsd);
#else
close(streamsd);
#endif
/*
* Receive the result.
*/
recvdata = recvfull(sockd, &len);
ck_assert_msg(!strcmp(recvdata, "stream: ClamAV-Test-File.UNOFFICIAL FOUND"),
ck_assert_msg(!strncmp(recvdata, "stream: ClamAV-Test-File.UNOFFICIAL FOUND", len),
"Wrong reply: %s\n", recvdata);
free(recvdata);
conn_teardown();
}
END_TEST
#endif
#define END_CMD "zEND"
#define INSTREAM_CMD "zINSTREAM"
@ -804,7 +891,7 @@ static void test_idsession_commands(int split, int instream)
ck_assert_msg(id > 0, "ID cannot be zero");
ck_assert_msg(id <= j, "ID too big: %u, max: %u\n", id, j);
q += 2;
ck_assert_msg(!strcmp(q, replies[id - 1]),
ck_assert_msg(NULL != strstr(q, replies[id - 1]),
"Wrong ID reply for ID %u: %s, expected %s\n",
id,
q, replies[id - 1]);
@ -843,12 +930,18 @@ static Suite *test_clamd_suite(void)
tcase_add_loop_test(tc_commands, test_basic_commands, 0, sizeof(basic_tests) / sizeof(basic_tests[0]));
tcase_add_loop_test(tc_commands, test_compat_commands, 0, sizeof(basic_tests) / sizeof(basic_tests[0]));
#ifndef _WIN32 // Disabled on Windows because fd-passing not supported on Windows
tcase_add_loop_test(tc_commands, test_fildes, 0, 4 * sizeof(fildes_cmds) / sizeof(fildes_cmds[0]));
#endif
tcase_add_test(tc_commands, test_stats);
tcase_add_test(tc_commands, test_instream);
#ifndef _WIN32 // Disabled on Windows because of issues getting the test to work, and because zSTREAM was deprecated in 02/2009 in favor of zINSTREAM.
tcase_add_test(tc_commands, test_stream);
#endif
tcase_add_test(tc_commands, test_idsession);
#ifndef _WIN32 // Disabled because fd-passing not supported on Windows
tc_stress = tcase_create("clamd stress test");
suite_add_tcase(s, tc_stress);
tcase_set_timeout(tc_stress, 20);
@ -860,19 +953,29 @@ static Suite *test_clamd_suite(void)
* tcp/unix sockets, if I too quickly connect ~193 times, even if
* listen backlog is higher.
* Don't run this test on BSD for now */
tcase_add_test(tc_stress, test_connections);
tcase_add_test(tc_stress, test_connections); // Disabled on Windows because test uses fork() instead of threads, and needs to be rewritten.
#endif
#endif
return s;
}
int main(void)
{
int nf;
int num_fds;
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) {
fprintf(stderr, "Error at WSAStartup(): %d\n", WSAGetLastError());
return EXIT_FAILURE;
}
#endif
Suite *s = test_clamd_suite();
SRunner *sr = srunner_create(s);
srunner_set_log(sr, BUILDDIR "/test-clamd.log");
srunner_set_log(sr, BUILDDIR PATHSEP "test-clamd.log");
srunner_run_all(sr, CK_NORMAL);
nf = srunner_ntests_failed(sr);
num_fds = srunner_ntests_failed(sr);
srunner_free(sr);
return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
return (num_fds == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}

@ -126,7 +126,7 @@ scan_failed() {
die "$2";
}
# ----------- valgrind wrapper
# ----------- valgrind wrapper
init_valgrind() {
test "x$VG" = "x1" || { echo "*** valgrind tests skipped by default, use 'make check VG=1' to activate"; exit 77; }
VALGRIND=`which ${VALGRIND-valgrind}` || true
@ -145,7 +145,7 @@ init_helgrind() {
end_valgrind() {
VLOG=valgrind$1.log
NRUNS=`grep -a "ERROR SUMMARY" $VLOG | wc -l`
if test $NRUNS -ne `grep -a "ERROR SUMMARY: 0 errors" $VLOG | wc -l` ||
if test $NRUNS -ne `grep -a "ERROR SUMMARY: 0 errors" $VLOG | wc -l` ||
test `grep -a "FATAL:" $VLOG|wc -l` -ne 0; then
cat $VLOG
die "Valgrind tests failed"
@ -285,19 +285,19 @@ run_clamdscan() {
rm -f clamdscan-fdpass.log clamdscan-multiscan-fdpass.log clamdscan-stream.log clamdscan-multiscan-stream.log
set +e
$CLAMDSCAN --quiet --config-file=test-clamd.conf $* --fdpass --log=clamdscan-fdpass.log
if test $? = 2; then
if test $? = 2; then
die "Failed to run clamdscan (fdpass)!"
fi
$CLAMDSCAN --quiet --config-file=test-clamd.conf $* -m --fdpass --log=clamdscan-multiscan-fdpass.log
if test $? = 2; then
if test $? = 2; then
die "Failed to run clamdscan (fdpass + multiscan)!"
fi
$CLAMDSCAN --quiet --config-file=test-clamd.conf $* --stream --log=clamdscan-stream.log
if test $? = 2; then
if test $? = 2; then
die "Failed to run clamdscan (instream)!"
fi
$CLAMDSCAN --quiet --config-file=test-clamd.conf $* -m --stream --log=clamdscan-multiscan-stream.log
if test $? = 2; then
if test $? = 2; then
die "Failed to run clamdscan (instream + multiscan)!"
fi
set -e

@ -29,9 +29,12 @@
#include <check.h>
#include <stdlib.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
// libclamav
#include "clamav.h"
@ -42,8 +45,10 @@
START_TEST(test_disasm_basic)
{
char file[] = "disasmXXXXXX";
int fd = mkstemp(file), ref;
int fd = -1;
char *temp_file_path = NULL;
cli_gentempfd_with_prefix(NULL, "disasm", &temp_file_path, &fd);
int ref;
uint8_t buf[] = {
/* m00/rm000 - add [eax], al */
0x00,
@ -214,7 +219,7 @@ START_TEST(test_disasm_basic)
STATBUF st;
ck_assert_msg(fd != -1, "mkstemp failed");
ref = open_testfile("input/disasmref.bin");
ref = open_testfile("input" PATHSEP "disasmref.bin", O_RDONLY | O_BINARY);
ck_assert_msg(FSTAT(ref, &st) != -1, "fstat failed");
disasmbuf(buf, sizeof(buf), fd);
size = lseek(fd, 0, SEEK_CUR);
@ -228,7 +233,8 @@ START_TEST(test_disasm_basic)
close(ref);
ck_assert_msg(!memcmp(d, d + size, size), "disasm data doesn't match the reference");
free(d);
unlink(file);
unlink(temp_file_path);
free(temp_file_path);
}
END_TEST

@ -20,6 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#undef pid_t
#include <check.h>
#include <fcntl.h>
#include <string.h>
@ -60,11 +61,11 @@ static struct test {
const char *jsref;
} tests[] = {
/* NULL means don't test it */
{"input/htmlnorm_buf.html", "buf.nocomment.ref", "buf.notags.ref", NULL},
{"input/htmlnorm_encode.html", "encode.nocomment.ref", NULL, "encode.js.ref"},
{"input/htmlnorm_js_test.html", "js.nocomment.ref", NULL, "js.js.ref"},
{"input/htmlnorm_test.html", "test.nocomment.ref", "test.notags.ref", NULL},
{"input/htmlnorm_urls.html", "urls.nocomment.ref", "urls.notags.ref", NULL}};
{"input" PATHSEP "htmlnorm_buf.html", "buf.nocomment.ref", "buf.notags.ref", NULL},
{"input" PATHSEP "htmlnorm_encode.html", "encode.nocomment.ref", NULL, "encode.js.ref"},
{"input" PATHSEP "htmlnorm_js_test.html", "js.nocomment.ref", NULL, "js.js.ref"},
{"input" PATHSEP "htmlnorm_test.html", "test.nocomment.ref", "test.notags.ref", NULL},
{"input" PATHSEP "htmlnorm_urls.html", "urls.nocomment.ref", "urls.notags.ref", NULL}};
static void check_dir(const char *dire, const struct test *test)
{
@ -72,37 +73,34 @@ static void check_dir(const char *dire, const struct test *test)
int fd, reffd;
if (test->nocommentref) {
snprintf(filename, sizeof(filename), "%s/nocomment.html", dire);
fd = open(filename, O_RDONLY);
snprintf(filename, sizeof(filename), "%s" PATHSEP "nocomment.html", dire);
fd = open(filename, O_RDONLY | O_BINARY);
ck_assert_msg(fd > 0, "unable to open: %s", filename);
reffd = open_testfile(test->nocommentref);
reffd = open_testfile(test->nocommentref, O_RDONLY | O_BINARY);
diff_files(fd, reffd);
close(reffd);
close(fd);
/* diff_files will close both fd's */
}
if (test->notagsref) {
snprintf(filename, sizeof(filename), "%s/notags.html", dire);
fd = open(filename, O_RDONLY);
snprintf(filename, sizeof(filename), "%s" PATHSEP "notags.html", dire);
fd = open(filename, O_RDONLY | O_BINARY);
ck_assert_msg(fd > 0, "unable to open: %s", filename);
reffd = open_testfile(test->notagsref);
reffd = open_testfile(test->notagsref, O_RDONLY | O_BINARY);
diff_files(fd, reffd);
close(reffd);
close(fd);
/* diff_files will close both fd's */
}
if (test->jsref) {
snprintf(filename, sizeof(filename), "%s/javascript", dire);
fd = open(filename, O_RDONLY);
snprintf(filename, sizeof(filename), "%s" PATHSEP "javascript", dire);
fd = open(filename, O_RDONLY | O_BINARY);
ck_assert_msg(fd > 0, "unable to open: %s", filename);
reffd = open_testfile(test->jsref);
reffd = open_testfile(test->jsref, O_RDONLY | O_BINARY);
diff_files(fd, reffd);
close(reffd);
close(fd);
/* diff_files will close both fd's */
}
}
@ -114,31 +112,31 @@ START_TEST(test_htmlnorm_api)
memset(&hrefs, 0, sizeof(hrefs));
fd = open_testfile(tests[_i].input);
fd = open_testfile(tests[_i].input, O_RDONLY | O_BINARY);
ck_assert_msg(fd > 0, "open_testfile failed");
map = fmap(fd, 0, 0, tests[_i].input);
ck_assert_msg(!!map, "fmap failed");
ck_assert_msg(mkdir(dir, 0700) == 0, "mkdir failed");
ck_assert_msg(mkdir(dir, 0700) == 0, "mkdir failed: %s", dir);
ck_assert_msg(html_normalise_map(map, dir, NULL, dconf) == 1, "html_normalise_map failed");
check_dir(dir, &tests[_i]);
ck_assert_msg(cli_rmdirs(dir) == 0, "rmdirs failed");
ck_assert_msg(cli_rmdirs(dir) == 0, "rmdirs failed: %s", dir);
ck_assert_msg(mkdir(dir, 0700) == 0, "mkdir failed");
ck_assert_msg(mkdir(dir, 0700) == 0, "mkdir failed: %s", dir);
ck_assert_msg(html_normalise_map(map, dir, NULL, NULL) == 1, "html_normalise_map failed");
ck_assert_msg(cli_rmdirs(dir) == 0, "rmdirs failed");
ck_assert_msg(cli_rmdirs(dir) == 0, "rmdirs failed: %s", dir);
ck_assert_msg(mkdir(dir, 0700) == 0, "mkdir failed");
ck_assert_msg(mkdir(dir, 0700) == 0, "mkdir failed: %s", dir);
ck_assert_msg(html_normalise_map(map, dir, &hrefs, dconf) == 1, "html_normalise_map failed");
ck_assert_msg(cli_rmdirs(dir) == 0, "rmdirs failed");
ck_assert_msg(cli_rmdirs(dir) == 0, "rmdirs failed: %s", dir);
html_tag_arg_free(&hrefs);
memset(&hrefs, 0, sizeof(hrefs));
hrefs.scanContents = 1;
ck_assert_msg(mkdir(dir, 0700) == 0, "mkdir failed");
ck_assert_msg(mkdir(dir, 0700) == 0, "mkdir failed: %s", dir);
ck_assert_msg(html_normalise_map(map, dir, &hrefs, dconf) == 1, "html_normalise_map failed");
ck_assert_msg(cli_rmdirs(dir) == 0, "rmdirs failed");
ck_assert_msg(cli_rmdirs(dir) == 0, "rmdirs failed: %s", dir);
html_tag_arg_free(&hrefs);
funmap(map);
@ -149,7 +147,7 @@ END_TEST
START_TEST(test_screnc_nullterminate)
{
int fd = open_testfile("input/screnc_test");
int fd = open_testfile("input" PATHSEP "screnc_test", O_RDONLY | O_BINARY);
fmap_t *map;
ck_assert_msg(mkdir(dir, 0700) == 0, "mkdir failed");

@ -29,6 +29,7 @@
#include <limits.h>
#include <string.h>
#include <check.h>
#include <fcntl.h>
// libclamav
#include "clamav.h"
@ -316,7 +317,7 @@ static void psetup_impl(int load2)
rc = init_domainlist(engine);
ck_assert_msg(rc == CL_SUCCESS, "init_domainlist");
f = fdopen(open_testfile("input/daily.pdb"), "r");
f = fdopen(open_testfile("input" PATHSEP "daily.pdb", O_RDONLY | O_BINARY), "r");
ck_assert_msg(!!f, "fopen daily.pdb");
rc = load_regex_matcher(engine, engine->domainlist_matcher, f, &signo, 0, 0, NULL, 1);
@ -326,7 +327,7 @@ static void psetup_impl(int load2)
ck_assert_msg(signo == 201, "Incorrect number of signatures: %u, expected %u", signo, 201);
if (load2) {
f = fdopen(open_testfile("input/daily.gdb"), "r");
f = fdopen(open_testfile("input" PATHSEP "daily.gdb", O_RDONLY | O_BINARY), "r");
ck_assert_msg(!!f, "fopen daily.gdb");
signo = 0;
@ -341,7 +342,7 @@ static void psetup_impl(int load2)
rc = init_whitelist(engine);
ck_assert_msg(rc == CL_SUCCESS, "init_whitelist");
f = fdopen(open_testfile("input/daily.wdb"), "r");
f = fdopen(open_testfile("input" PATHSEP "daily.wdb", O_RDONLY | O_BINARY), "r");
signo = 0;
rc = load_regex_matcher(engine, engine->whitelist_matcher, f, &signo, 0, 1, NULL, 1);
ck_assert_msg(rc == CL_SUCCESS, "load_regex_matcher");
@ -620,7 +621,7 @@ END_TEST
START_TEST(phishing_fake_test)
{
char buf[4096];
FILE *f = fdopen(open_testfile("input/daily.pdb"), "r");
FILE *f = fdopen(open_testfile("input" PATHSEP "daily.pdb", O_RDONLY | O_BINARY), "r");
ck_assert_msg(!!f, "fopen daily.pdb");
while (fgets(buf, sizeof(buf), f)) {
struct rtest rtest;
@ -641,7 +642,7 @@ END_TEST
START_TEST(phishing_fake_test_allscan)
{
char buf[4096];
FILE *f = fdopen(open_testfile("input/daily.pdb"), "r");
FILE *f = fdopen(open_testfile("input" PATHSEP "daily.pdb", O_RDONLY | O_BINARY), "r");
ck_assert_msg(!!f, "fopen daily.pdb");
while (fgets(buf, sizeof(buf), f)) {
struct rtest rtest;

@ -1,7 +1,6 @@
#ifndef CHECKS_H
#define CHECKS_H
#include "checks_common.h"
Suite *test_jsnorm_suite(void);
Suite *test_str_suite(void);
Suite *test_regex_suite(void);
@ -11,7 +10,7 @@ Suite *test_matchers_suite(void);
Suite *test_htmlnorm_suite(void);
Suite *test_bytecode_suite(void);
void errmsg_expected(void);
int open_testfile(const char *name);
int open_testfile(const char *name, int flags);
void diff_files(int fd, int reffd);
void diff_file_mem(int fd, const char *ref, size_t len);

@ -1,4 +0,0 @@
#ifndef CHECKS_COMMON_H
#define CHECKS_COMMON_H
#endif

@ -0,0 +1,455 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
"""
Run clamd tests.
"""
import os
from pathlib import Path
import platform
import socket
import subprocess
import shutil
import sys
import time
import unittest
import testcase
os_platform = platform.platform()
operating_system = os_platform.split('-')[0].lower()
def check_port_available(port_num: int) -> bool:
'''
Check if port # is available
'''
port_is_available = True # It's probably available...
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
location = ("127.0.0.1", port_num)
result_of_check = sock.connect_ex(location)
if result_of_check == 0:
port_is_available = False # Oh nevermind! Someone was listening!
sock.close()
return port_is_available
class TC(testcase.TestCase):
@classmethod
def setUpClass(cls):
super(TC, cls).setUpClass()
TC.testpaths = list(TC.path_build.glob('test/clam*')) # A list of Path()'s of each of our generated test files
TC.clamd_pid = TC.path_tmp / 'clamd-test.pid'
TC.clamd_socket = TC.path_build / 'unit_tests' / 'clamd-test.socket' # <-- this is hard-coded into the `check_clamd` program
TC.clamd_port_num = 3319 # <-- this is hard-coded into the `check_clamd` program
TC.path_db = TC.path_tmp / 'database'
TC.path_db.mkdir(parents=True)
shutil.copy(
str(TC.path_build / 'unit_tests' / 'clamav.hdb'),
str(TC.path_db),
)
shutil.copy(
str(TC.path_source / 'unit_tests' / 'input' / 'daily.pdb'),
str(TC.path_db),
)
# Identify a TCP port we can use.
# Presently disabled because check_clamd's port # is hardcoded.
#found_open_port = False
#for port_num in range(3310, 3410):
# if check_port_available(port_num) == True:
# found_open_port = True
# break
#assert found_open_port == True
# Prep a clamd.conf to use for most (if not all) of the tests.
config = f'''
Foreground yes
PidFile {TC.clamd_pid}
DatabaseDirectory {TC.path_db}
LogFileMaxSize 0
LogTime yes
#Debug yes
LogClean yes
LogVerbose yes
ExitOnOOM yes
DetectPUA yes
ScanPDF yes
CommandReadTimeout 1
MaxQueue 800
MaxConnectionQueueLength 1024
'''
if operating_system == 'windows':
# Only have TCP socket option for Windows.
config += f'''
TCPSocket {TC.clamd_port_num}
TCPAddr 127.0.0.1
'''
else:
# Use LocalSocket for Posix, because that's what check_clamd expects.
config += f'''
LocalSocket {TC.clamd_socket}
TCPSocket {TC.clamd_port_num}
TCPAddr 127.0.0.1
'''
TC.clamd_config = TC.path_tmp / 'clamd-test.conf'
TC.clamd_config.write_text(config)
# Check if fdpassing is supported.
TC.has_fdpass_support = False
with (TC.path_build / 'clamav-config.h').open('r') as clamav_config:
if "#define HAVE_FD_PASSING 1" in clamav_config.read():
TC.has_fdpass_support = True
@classmethod
def tearDownClass(cls):
super(TC, cls).tearDownClass()
def setUp(self):
super(TC, self).setUp()
self.proc = None
def tearDown(self):
super(TC, self).tearDown()
# Kill clamd (if running)
if self.proc != None:
try:
self.proc.terminate()
self.proc.wait(timeout=120)
self.proc.stdin.close()
except OSError as exc:
self.log.warning(f'Unexpected exception {exc}')
pass # ignore
self.proc = None
TC.clamd_pid.unlink(missing_ok=True)
TC.clamd_socket.unlink(missing_ok=True)
self.verify_valgrind_log()
def start_clamd(self):
'''
Start clamd
'''
command = f'{TC.valgrind} {TC.valgrind_args} {TC.clamd} --config-file={TC.clamd_config}'
self.log.info(f'Starting clamd: {command}')
self.proc = subprocess.Popen(
command.strip().split(' '),
stdin=subprocess.PIPE,
stdout=sys.stdout.buffer,
stderr=sys.stdout.buffer,
)
def run_clamdscan(self,
scan_args,
expected_ec=0,
expected_out=[],
expected_err=[],
unexpected_out=[],
unexpected_err=[]):
'''
Run clamdscan in each mode
The first scan uses ping & wait to give clamd time to start.
'''
# default (filepath) mode
output = self.execute_command(f'{TC.clamdscan} --ping 5 --wait -c {TC.clamd_config} {scan_args}')
assert output.ec == expected_ec
if expected_out != [] or unexpected_out != []:
self.verify_output(output.out, expected=expected_out, unexpected=unexpected_out)
if expected_err != [] or unexpected_err != []:
self.verify_output(output.err, expected=expected_err, unexpected=unexpected_err)
# multi mode
output = self.execute_command(f'{TC.clamdscan} -c {TC.clamd_config} -m {scan_args}')
assert output.ec == expected_ec
if expected_out != [] or unexpected_out != []:
self.verify_output(output.out, expected=expected_out, unexpected=unexpected_out)
if expected_err != [] or unexpected_err != []:
self.verify_output(output.err, expected=expected_err, unexpected=unexpected_err)
if TC.has_fdpass_support:
# fdpass
output = self.execute_command(f'{TC.clamdscan} -c {TC.clamd_config} --fdpass {scan_args}')
assert output.ec == expected_ec
if expected_out != [] or unexpected_out != []:
self.verify_output(output.out, expected=expected_out, unexpected=unexpected_out)
if expected_err != [] or unexpected_err != []:
self.verify_output(output.err, expected=expected_err, unexpected=unexpected_err)
# fdpass multi mode
output = self.execute_command(f'{TC.clamdscan} -c {TC.clamd_config} --fdpass -m {scan_args}')
assert output.ec == expected_ec
if expected_out != [] or unexpected_out != []:
self.verify_output(output.out, expected=expected_out, unexpected=unexpected_out)
if expected_err != [] or unexpected_err != []:
self.verify_output(output.err, expected=expected_err, unexpected=unexpected_err)
# stream
output = self.execute_command(f'{TC.clamdscan} -c {TC.clamd_config} --stream {scan_args}')
assert output.ec == expected_ec
if expected_out != [] or unexpected_out != []:
self.verify_output(output.out, expected=expected_out, unexpected=unexpected_out)
if expected_err != [] or unexpected_err != []:
self.verify_output(output.err, expected=expected_err, unexpected=unexpected_err)
# stream multi mode
output = self.execute_command(f'{TC.clamdscan} -c {TC.clamd_config} --stream -m {scan_args}')
assert output.ec == expected_ec
if expected_out != [] or unexpected_out != []:
self.verify_output(output.out, expected=expected_out, unexpected=unexpected_out)
if expected_err != [] or unexpected_err != []:
self.verify_output(output.err, expected=expected_err, unexpected=unexpected_err)
def run_clamdscan_file_only(self,
scan_args,
expected_ec=0,
expected_out=[],
expected_err=[],
unexpected_out=[],
unexpected_err=[]):
'''
Run clamdscan in filepath mode (and filepath multi mode)
The first scan uses ping & wait to give clamd time to start.
'''
# default mode
output = self.execute_command(f'{TC.clamdscan} --ping 5 --wait -c {TC.clamd_config} {scan_args}')
assert output.ec == expected_ec
if expected_out != [] or unexpected_out != []:
self.verify_output(output.out, expected=expected_out, unexpected=unexpected_out)
if expected_err != [] or unexpected_err != []:
self.verify_output(output.err, expected=expected_err, unexpected=unexpected_err)
# multi mode
output = self.execute_command(f'{TC.clamdscan} -c {TC.clamd_config} -m {scan_args}')
assert output.ec == expected_ec
if expected_out != [] or unexpected_out != []:
self.verify_output(output.out, expected=expected_out, unexpected=unexpected_out)
if expected_err != [] or unexpected_err != []:
self.verify_output(output.err, expected=expected_err, unexpected=unexpected_err)
def run_clamdscan_fdpass_only(self,
scan_args,
expected_ec=0,
expected_out=[],
expected_err=[],
unexpected_out=[],
unexpected_err=[]):
'''
Run clamdscan fdpass mode only
Use ping & wait to give clamd time to start.
'''
# fdpass
output = self.execute_command(f'{TC.clamdscan} --ping 5 --wait -c {TC.clamd_config} --fdpass {scan_args}')
assert output.ec == expected_ec
if expected_out != [] or unexpected_out != []:
self.verify_output(output.out, expected=expected_out, unexpected=unexpected_out)
if expected_err != [] or unexpected_err != []:
self.verify_output(output.err, expected=expected_err, unexpected=unexpected_err)
def test_clamd_00_version(self):
'''
verify that clamd -v returns the version
'''
self.step_name('clamd version test')
command = f'{TC.valgrind} {TC.valgrind_args} {TC.clamd} --config-file={TC.clamd_config} -V'
output = self.execute_command(command)
assert output.ec == 0 # success
expected_results = [
f'ClamAV {TC.version}',
]
self.verify_output(output.out, expected=expected_results)
def test_clamd_01_ping_pong(self):
'''
Verify that clamd responds to a PING command
'''
self.step_name('Testing clamd + clamdscan PING PONG feature')
self.start_clamd()
poll = self.proc.poll()
assert poll == None # subprocess is alive if poll() returns None
output = self.execute_command(f'{TC.clamdscan} -p 5 -c {TC.clamd_config}')
assert output.ec == 0 # success
self.verify_output(output.out, expected=['PONG'])
def test_clamd_02_clamdscan_version(self):
'''
Verify that clamdscan --version returns the expected version #
Explanation: clamdscan --version will query clamd for it's version
and print out clamd's version. If it can't connect to clamd, it'll
throw and error saying as much and then report it's own version.
In this test, we want to check clamd's version through clamdscan.
'''
self.step_name('Testing clamd + clamdscan version feature')
self.start_clamd()
poll = self.proc.poll()
assert poll == None # subprocess is alive if poll() returns None
# First we'll ping-pong to make sure clamd is up
# If clamd isn't up before the version test, clamdscan will return it's
# own version, which isn't really the point of the test.
output = self.execute_command(f'{TC.clamdscan} --ping 5 -c {TC.clamd_config}')
assert output.ec == 0 # success
self.verify_output(output.out, expected=['PONG'])
# Ok now it's up, let's check clamd's version via clamdscan.
output = self.execute_command(f'{TC.clamdscan} --version -c {TC.clamd_config}')
assert output.ec == 0 # success
self.verify_output(output.out,
expected=[f'ClamAV {TC.version}'], unexpected=['Could not connect to clamd'])
def test_clamd_03_reload(self):
'''
In this test, it is not supposed to detect until we actually put the
signature there and reload!
'''
self.step_name('Test scan before & after reload')
self.start_clamd()
poll = self.proc.poll()
assert poll == None # subprocess is alive if poll() returns None
(TC.path_tmp / 'reload-testfile').write_bytes(b'ClamAV-RELOAD-Test')
self.run_clamdscan(f'{TC.path_tmp / "reload-testfile"}',
expected_ec=0, expected_out=['reload-testfile: OK', 'Infected files: 0'])
(TC.path_db / 'reload-test.ndb').write_text('ClamAV-RELOAD-TestFile:0:0:436c616d41562d52454c4f41442d54657374')
output = self.execute_command(f'{TC.clamdscan} --reload -c {TC.clamd_config}')
assert output.ec == 0 # success
time.sleep(2) # give clamd a moment to reload before trying again
# with multi-threaded reloading will clamd would happily
# re-scan with the old engine while it reloads.
self.run_clamdscan(f'{TC.path_tmp / "reload-testfile"}',
expected_ec=1, expected_out=['ClamAV-RELOAD-TestFile.UNOFFICIAL FOUND', 'Infected files: 1'])
def test_clamd_04_all_testfiles(self):
'''
Verify that clamd + clamdscan detect each of our <build>/test/clam* test files.
'''
self.step_name('Testing clamd + clamdscan scan of all `test` files')
self.start_clamd()
poll = self.proc.poll()
assert poll == None # subprocess is alive if poll() returns None
testfiles = ' '.join([str(testpath) for testpath in TC.testpaths])
expected_results = [f'{testpath.name}: ClamAV-Test-File.UNOFFICIAL FOUND' for testpath in TC.testpaths]
expected_results.append(f'Infected files: {len(TC.testpaths)}')
self.run_clamdscan(f'{testfiles}',
expected_ec=1, expected_out=expected_results)
def test_clamd_05_check_clamd(self):
'''
Uses the check_clamd program to test clamd's socket API in various ways
that aren't possible with clamdscan.
'''
self.step_name('Testing clamd + check_clamd')
self.start_clamd()
poll = self.proc.poll()
assert poll == None # subprocess is alive if poll() returns None
# Let's first use the ping-pong test to make sure clamd is listening.
output = self.execute_command(f'{TC.clamdscan} -p 5 -c {TC.clamd_config}')
assert output.ec == 0 # success
self.verify_output(output.out, expected=['PONG'])
# Ok now run check_clamd to have fun with clamd's API
output = self.execute_command(f'{TC.check_clamd}')
assert output.ec == 0 # success
expected_results = [
'100%', 'Failures: 0', 'Errors: 0'
]
self.verify_output(output.out, expected=expected_results)
# Let's do another ping-pong test to see if `check_clamd` killed clamd (Mu-ha-ha).
output = self.execute_command(f'{TC.clamdscan} -p 5 -c {TC.clamd_config}')
assert output.ec == 0 # success
self.verify_output(output.out, expected=['PONG'])
def test_clamd_06_HeuristicScanPrecedence_off(self):
'''
Verify that HeuristicScanPrecedence off works as expected (default)
In a later test, we'll add `HeuristicScanPrecedence yes` to the config
and retest with it on.
With it off, we expect the scan to complete and the "real" virus to alert
rather than the heuristic.
'''
self.step_name('Testing clamd + clamdscan w/ HeuristicScanPrecedence no (default)')
self.start_clamd()
poll = self.proc.poll()
assert poll == None # subprocess is alive if poll() returns None
self.run_clamdscan(f'{TC.path_build / "unit_tests" / "clam-phish-exe"}',
expected_ec=1, expected_out=['ClamAV-Test-File'])
def test_clamd_07_HeuristicScanPrecedence_on(self):
'''
Verify that HeuristicScanPrecedence on works as expected.
With it on, we expect the scan to stop and raise an alert as soon as
the phishing heuristic is detected.
'''
self.step_name('Testing clamd + clamdscan w/ HeuristicScanPrecedence yes')
with TC.clamd_config.open('a') as config:
config.write('''
HeuristicScanPrecedence yes
''')
self.start_clamd()
poll = self.proc.poll()
assert poll == None # subprocess is alive if poll() returns None
self.run_clamdscan(f'{TC.path_build / "unit_tests" / "clam-phish-exe"}',
expected_ec=1, expected_out=['Heuristics.Phishing.Email.SpoofedDomain'])
@unittest.skipIf(operating_system == 'windows', 'This test uses a shell script to test virus-action. TODO: add Windows support to this test.')
def test_clamd_08_VirusEvent(self):
'''
Test that VirusEvent works
'''
self.step_name('Testing clamd + clamdscan w/ VirusEvent')
with TC.clamd_config.open('a') as config:
config.write(f'VirusEvent {TC.path_source / "unit_tests" / "virusaction-test.sh"} {TC.path_tmp} "Virus found: %v"\n')
self.start_clamd()
poll = self.proc.poll()
assert poll == None # subprocess is alive if poll() returns None
self.run_clamdscan_file_only(f'{TC.path_build / "test" / "clam.exe"}',
expected_ec=1)#, expected_out=['Virus found: ClamAV-Test-File.UNOFFICIAL'])
self.log.info(f'verifying log output from virusaction-test.sh: {str(TC.path_tmp / "test-clamd.log")}')
self.verify_log(str(TC.path_tmp / 'test-clamd.log'),
expected=['Virus found: ClamAV-Test-File.UNOFFICIAL'],
unexpected=['VirusEvent incorrect', 'VirusName incorrect'])

@ -0,0 +1,220 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
"""
Run clamscan tests.
"""
import os
from pathlib import Path
import platform
import shutil
import subprocess
import sys
import time
import unittest
import testcase
os_platform = platform.platform()
operating_system = os_platform.split('-')[0].lower()
class TC(testcase.TestCase):
@classmethod
def setUpClass(cls):
super(TC, cls).setUpClass()
TC.testpaths = list(TC.path_build.glob('test/clam*')) # A list of Path()'s of each of our generated test files
# Prepare a directory to store our test databases
TC.path_db = TC.path_tmp / 'database'
TC.path_db.mkdir(parents=True)
shutil.copy(
str(TC.path_build / 'unit_tests' / 'clamav.hdb'),
str(TC.path_db),
)
(TC.path_db / 'clamav.ign2').write_text('ClamAV-Test-File\n')
(TC.path_db / 'phish.pdb').write_text('H:example.com\n')
(TC.path_db / 'icon.idb').write_text(
"EA0X-32x32x8:ea0x-grp1:ea0x-grp2:2046f030a42a07153f4120a0031600007000005e1617ef0000d21100cb090674150f880313970b0e7716116d01136216022500002f0a173700081a004a0e\n"
"IScab-16x16x8:iscab-grp1:iscab-grp2:107b3000168306015c20a0105b07060be0a0b11c050bea0706cb0a0bbb060b6f00017c06018301068109086b03046705081b000a270a002a000039002b17\n"
)
(TC.path_db / 'icon.ldb').write_text(
"ClamAV-Test-Icon-EA0X;Engine:52-1000,Target:1,IconGroup1:ea0x-grp1,IconGroup2:*;(0);0:4d5a\n"
"ClamAV-Test-Icon-IScab;Engine:52-1000,Target:1,IconGroup2:iscab-grp2;(0);0:4d5a\n"
)
(TC.path_db / 'Clam-VI.ldb').write_text(
"Clam-VI-Test:Target;Engine:52-255,Target:1;(0&1);VI:43006f006d00700061006e0079004e0061006d0065000000000063006f006d00700061006e007900;VI:500072006f0064007500630074004e0061006d0065000000000063006c0061006d00\n"
)
(TC.path_db / 'yara-at-offset.yara').write_text(
"rule yara_at_offset {strings: $tar_magic = { 75 73 74 61 72 } condition: $tar_magic at 257}\n"
)
(TC.path_db / 'yara-in-range.yara').write_text(
"rule yara_in_range {strings: $tar_magic = { 75 73 74 61 72 } condition: $tar_magic in (200..300)}\n"
)
@classmethod
def tearDownClass(cls):
super(TC, cls).tearDownClass()
def setUp(self):
super(TC, self).setUp()
def tearDown(self):
super(TC, self).tearDown()
self.verify_valgrind_log()
def test_clamscan_00_version(self):
self.step_name('clamscan version test')
command = f'{TC.valgrind} {TC.valgrind_args} {TC.clamscan} -V'
output = self.execute_command(command)
assert output.ec == 0 # success
expected_results = [
f'ClamAV {TC.version}',
]
self.verify_output(output.out, expected=expected_results)
def test_clamscan_01_all_testfiles(self):
self.step_name('Test that clamscan alerts on all test files')
testfiles = ' '.join([str(testpath) for testpath in TC.testpaths])
command = f'{TC.valgrind} {TC.valgrind_args} {TC.clamscan} -d {TC.path_db / "clamav.hdb"} {testfiles}'
output = self.execute_command(command)
assert output.ec == 1 # virus found
expected_results = [f'{testpath.name}: ClamAV-Test-File.UNOFFICIAL FOUND' for testpath in TC.testpaths]
expected_results.append(f'Scanned files: {len(TC.testpaths)}')
expected_results.append(f'Infected files: {len(TC.testpaths)}')
self.verify_output(output.out, expected=expected_results)
def test_clamscan_02_all_testfiles_ign2(self):
self.step_name('Test that clamscan ignores ClamAV-Test-File alerts')
testfiles = ' '.join([str(testpath) for testpath in TC.testpaths])
command = f'{TC.valgrind} {TC.valgrind_args} {TC.clamscan} -d {TC.path_db / "clamav.hdb"} -d {TC.path_db / "clamav.ign2"} {testfiles}'
output = self.execute_command(command)
assert output.ec == 1 # virus found
expected_results = [f'{testpath.name}: ClamAV-Test-File.UNOFFICIAL FOUND' for testpath in TC.testpaths]
expected_results.append(f'Scanned files: {len(TC.testpaths)}')
expected_results.append(f'Infected files: {len(TC.testpaths)}')
self.verify_output(output.out, expected=expected_results)
def test_clamscan_03_phish_test_not_enabled(self):
self.step_name('Test that clamscan will load the phishing sigs w/out issue')
testpaths = list(TC.path_source.glob('unit_tests/input/phish-test-*'))
testfiles = ' '.join([str(testpath) for testpath in testpaths])
command = f'{TC.valgrind} {TC.valgrind_args} {TC.clamscan} -d {TC.path_db / "phish.pdb"} {testfiles}'
output = self.execute_command(command)
assert output.ec == 0 # virus NOT found
expected_results = [
'Scanned files: 3',
'Infected files: 0',
]
self.verify_output(output.out, expected=expected_results)
def test_clamscan_04_phish_test_alert_phishing_ssl_alert_phishing_cloak(self):
self.step_name('Test clamscan --alert-phishing-ssl --alert-phishing-cloak')
testpaths = list(TC.path_source.glob('unit_tests/input/phish-test-*'))
testfiles = ' '.join([str(testpath) for testpath in testpaths])
command = f'{TC.valgrind} {TC.valgrind_args} {TC.clamscan} -d {TC.path_db / "phish.pdb"} --alert-phishing-ssl --alert-phishing-cloak {testfiles}'
output = self.execute_command(command)
assert output.ec == 1 # virus found
expected_results = [
'phish-test-ssl: Heuristics.Phishing.Email.SSL-Spoof FOUND',
'phish-test-cloak: Heuristics.Phishing.Email.Cloaked.Null FOUND',
'Scanned files: 3',
'Infected files: 2', # there's a clean one
]
self.verify_output(output.out, expected=expected_results)
def test_clamscan_05_icon(self):
self.step_name('Test icon (.ldb + .idb) signatures')
testfiles = ' '.join([str(testpath) for testpath in TC.testpaths])
command = f'{TC.valgrind} {TC.valgrind_args} {TC.clamscan} -d {TC.path_db / "icon.ldb"} -d {TC.path_db / "icon.idb"} {testfiles}'
output = self.execute_command(command)
assert output.ec == 1 # virus found
# Use check_fpu_endian to determine expected results
command = f'{TC.check_fpu_endian}'
fpu_endian_output = self.execute_command(command)
expected_results = [
'clam_IScab_ext.exe: ClamAV-Test-Icon-IScab.UNOFFICIAL FOUND',
'clam_IScab_int.exe: ClamAV-Test-Icon-IScab.UNOFFICIAL FOUND',
]
if fpu_endian_output.ec == 3:
expected_num_infected = 3
else:
expected_results.append('clam.ea06.exe: ClamAV-Test-Icon-EA0X.UNOFFICIAL FOUND')
expected_num_infected = 4
expected_results.append(f'Infected files: {expected_num_infected}')
self.verify_output(output.out, expected=expected_results)
def test_clamscan_06_LDB_VI(self):
self.step_name('Test LDB VI feature')
testfiles = ' '.join([str(testpath) for testpath in TC.testpaths])
command = f'{TC.valgrind} {TC.valgrind_args} {TC.clamscan} -d {TC.path_db / "Clam-VI.ldb"} {testfiles}'
output = self.execute_command(command)
assert output.ec == 1 # virus found
expected_results = [
'clam_ISmsi_ext.exe: Clam-VI-Test:Target.UNOFFICIAL FOUND',
'clam_ISmsi_int.exe: Clam-VI-Test:Target.UNOFFICIAL FOUND',
'Infected files: 2',
]
self.verify_output(output.out, expected=expected_results)
def test_clamscan_07_yara_at_offset(self):
self.step_name('Test yara signature - detect TAR file magic at an offset')
testfiles = ' '.join([str(testpath) for testpath in TC.testpaths])
command = f'{TC.valgrind} {TC.valgrind_args} {TC.clamscan} -d {TC.path_db / "yara-at-offset.yara"} {testfiles}'
output = self.execute_command(command)
assert output.ec == 1 # virus found
expected_results = [
'clam.tar.gz: YARA.yara_at_offset.UNOFFICIAL FOUND',
'clam_cache_emax.tgz: YARA.yara_at_offset.UNOFFICIAL FOUND',
'Infected files: 2',
]
self.verify_output(output.out, expected=expected_results)
def test_clamscan_08_yara_in_range(self):
self.step_name('Test yara signature - detect TAR file magic in a range')
testfiles = ' '.join([str(testpath) for testpath in TC.testpaths])
command = f'{TC.valgrind} {TC.valgrind_args} {TC.clamscan} -d {TC.path_db / "yara-in-range.yara"} {testfiles}'
output = self.execute_command(command)
assert output.ec == 1 # virus found
expected_results = [
'clam.tar.gz: YARA.yara_in_range.UNOFFICIAL FOUND',
'clam_cache_emax.tgz: YARA.yara_in_range.UNOFFICIAL FOUND',
'Infected files: 2',
]
self.verify_output(output.out, expected=expected_results)

@ -0,0 +1,89 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
"""
Run freshclam tests
"""
import os
from pathlib import Path
import platform
import shutil
import subprocess
import sys
import time
import unittest
import testcase
os_platform = platform.platform()
operating_system = os_platform.split('-')[0].lower()
class TC(testcase.TestCase):
@classmethod
def setUpClass(cls):
super(TC, cls).setUpClass()
# Prepare a directory to host our test databases
TC.path_www = Path(TC.path_tmp, 'www')
TC.path_www.mkdir()
shutil.copy(
str(TC.path_build / 'unit_tests' / 'clamav.hdb'),
str(TC.path_www),
)
TC.path_db = Path(TC.path_tmp, 'database')
TC.freshclam_pid = Path(TC.path_tmp, 'freshclam-test.pid')
TC.freshclam_config = Path(TC.path_tmp, 'freshclam-test.conf')
TC.freshclam_config.write_text(f'''
DatabaseMirror 127.0.0.1
PidFile {TC.freshclam_pid}
LogVerbose yes
LogFileMaxSize 0
LogTime yes
DatabaseDirectory {TC.path_db}
DatabaseCustomURL file://{TC.path_www / "clamav.hdb"}
ExcludeDatabase daily
ExcludeDatabase main
ExcludeDatabase bytecode
''')
@classmethod
def tearDownClass(cls):
super(TC, cls).tearDownClass()
def setUp(self):
super(TC, self).setUp()
def tearDown(self):
super(TC, self).tearDown()
self.verify_valgrind_log()
def test_freshclam_00_version(self):
self.step_name('freshclam version test')
command = f'{TC.valgrind} {TC.valgrind_args} {TC.freshclam} --config-file={TC.freshclam_config} -V'
output = self.execute_command(command)
assert output.ec == 0 # success
expected_results = [
f'ClamAV {TC.version}',
]
self.verify_output(output.out, expected=expected_results)
def test_freshclam_01_file_copy(self):
self.step_name('Basic freshclam test using file:// to "download" clamav.hdb')
command = f'{TC.valgrind} {TC.valgrind_args} {TC.freshclam} --config-file={TC.freshclam_config}'
output = self.execute_command(command)
assert output.ec == 0 # success
expected_results = [
f'Downloading clamav.hdb',
f'Database test passed.',
f'clamav.hdb updated',
]
self.verify_output(output.out, expected=expected_results)

@ -0,0 +1,50 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
"""
Run libclamav unit tests
"""
import os
from pathlib import Path
import platform
import subprocess
import sys
import time
import unittest
import testcase
os_platform = platform.platform()
operating_system = os_platform.split('-')[0].lower()
class TC(testcase.TestCase):
@classmethod
def setUpClass(cls):
super(TC, cls).setUpClass()
@classmethod
def tearDownClass(cls):
super(TC, cls).tearDownClass()
def setUp(self):
super(TC, self).setUp()
def tearDown(self):
super(TC, self).tearDown()
self.verify_valgrind_log()
def test_libclamav_00_unit_test(self):
self.step_name('libclamav unit tests')
# If no valgrind, valgrind nad valgrind args are empty strings
command = f'{TC.valgrind} {TC.valgrind_args} {TC.check_clamav}'
output = self.execute_command(command)
assert output.ec == 0 # success
expected_results = [
'100%', 'Failures: 0', 'Errors: 0'
]
self.verify_output(output.out, expected=expected_results)

@ -0,0 +1,76 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
"""
Run sigtool tests.
"""
import os
from pathlib import Path
import platform
import shutil
import subprocess
import sys
import time
import unittest
import testcase
os_platform = platform.platform()
operating_system = os_platform.split('-')[0].lower()
class TC(testcase.TestCase):
@classmethod
def setUpClass(cls):
super(TC, cls).setUpClass()
# Prepare a directory to host our test databases
TC.path_www = TC.path_tmp / 'www'
TC.path_www.mkdir()
shutil.copy(
str(TC.path_build / 'unit_tests' / 'clamav.hdb'),
str(TC.path_www),
)
TC.path_db = TC.path_tmp / 'database'
TC.sigtool_pid = TC.path_tmp / 'sigtool-test.pid'
TC.sigtool_config = TC.path_tmp / 'sigtool-test.conf'
TC.sigtool_config.write_text(f'''
DatabaseMirror 127.0.0.1
PidFile {TC.sigtool_pid}
LogVerbose yes
LogFileMaxSize 0
LogTime yes
DatabaseDirectory {TC.path_db}
DatabaseCustomURL file://{TC.path_www}/clamav.hdb
ExcludeDatabase daily
ExcludeDatabase main
ExcludeDatabase bytecode
''')
@classmethod
def tearDownClass(cls):
super(TC, cls).tearDownClass()
def setUp(self):
super(TC, self).setUp()
def tearDown(self):
super(TC, self).tearDown()
self.verify_valgrind_log()
def test_sigtool_00_version(self):
self.step_name('sigtool version test')
self.log.warning(f'VG: {os.getenv("VG")}')
command = f'{TC.valgrind} {TC.valgrind_args} {TC.sigtool} -V'
output = self.execute_command(command)
assert output.ec == 0 # success
expected_results = [
f'ClamAV {TC.version}',
]
self.verify_output(output.out, expected=expected_results)

@ -0,0 +1,918 @@
# Copyright (C) 2017-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
"""
Wrapper for unittest to provide ClamAV specific test environment features.
"""
from collections import namedtuple
import hashlib
import logging
import os
import platform
import re
import shutil
import signal
import subprocess
import sys
import tempfile
import threading
from typing import Union
import unittest
from pathlib import Path
EXECUTION_TIMEOUT = 200
TIMEOUT_EXIT_CODE = 111
STRICT_ORDER = 0
ANY_ORDER = 1
CHUNK_SIZE = 100
loggers = {}
class TestCase(unittest.TestCase):
"""
This wrapper around unittest.TestCase provides added utilities and environment information.
"""
version = ""
path_source = None
path_build = None
path_tmp = None
check_clamav = None
check_clamd = None
check_fpu_endian = None
milter = None
clambc = None
clamd = None
clamdscan = None
clamdtop = None
clamscan = None
clamsubmit = None
clamconf = None
clamonacc = None
freshclam = None
sigtool = None
path_sample_config = None
valgrind = "" # Not 'None' because we'll use this variable even if valgrind not found.
valgrind_args = ""
log_suffix = '.log'
@classmethod
def setUpClass(cls):
"""
Initialize, to provide logging and test paths.
Also initializes internal Executor and LogChecker required
for execute_command(), and verify_log()
"""
global loggers
if loggers.get(cls.__name__):
cls.log = loggers.get(cls.__name__)
else:
cls.log = Logger(cls.__name__)
loggers[cls.__name__] = cls.log
cls._executor = Executor()
cls._log_checker = LogChecker()
os_platform = platform.platform()
cls.operating_system = os_platform.split("-")[0].lower()
# Get test paths from environment variables.
cls.version = os.getenv("VERSION")
if cls.version == None:
raise Exception("VERSION environment variable not defined! Aborting...")
cls.path_source = Path(os.getenv("SOURCE"))
cls.path_build = Path(os.getenv("BUILD"))
cls.path_tmp = Path(tempfile.mkdtemp(prefix=(cls.__name__ + "-"), dir=os.getenv("TMP")))
cls.check_clamav = Path(os.getenv("CHECK_CLAMAV")) if os.getenv("CHECK_CLAMAV") != None else None
cls.check_clamd = Path(os.getenv("CHECK_CLAMD")) if os.getenv("CHECK_CLAMD") != None else None
cls.check_fpu_endian = Path(os.getenv("CHECK_FPU_ENDIAN")) if os.getenv("CHECK_FPU_ENDIAN") != None else None
cls.milter = Path(os.getenv("CLAMAV_MILTER")) if os.getenv("CLAMAV_MILTER") != None else None
cls.clambc = Path(os.getenv("CLAMBC")) if os.getenv("CLAMBC") != None else None
cls.clamd = Path(os.getenv("CLAMD")) if os.getenv("CLAMD") != None else None
cls.clamdscan = Path(os.getenv("CLAMDSCAN")) if os.getenv("CLAMDSCAN") != None else None
cls.clamdtop = Path(os.getenv("CLAMDTOP")) if os.getenv("CLAMDTOP") != None else None
cls.clamscan = Path(os.getenv("CLAMSCAN")) if os.getenv("CLAMSCAN") != None else None
cls.clamsubmit = Path(os.getenv("CLAMSUBMIT")) if os.getenv("CLAMSUBMIT") != None else None
cls.clamconf = Path(os.getenv("CLAMCONF")) if os.getenv("CLAMCONF") != None else None
cls.clamonacc = Path(os.getenv("CLAMONACC")) if os.getenv("CLAMONACC") != None else None
cls.freshclam = Path(os.getenv("FRESHCLAM")) if os.getenv("FRESHCLAM") != None else None
cls.sigtool = Path(os.getenv("SIGTOOL")) if os.getenv("SIGTOOL") != None else None
if cls.operating_system == "windows":
cls.path_sample_config = cls.path_source / "win32" / "conf_examples"
else:
cls.path_sample_config = cls.path_source / "etc"
# Check if Valgrind testing is requested
if os.getenv('VALGRIND') != None:
cls.log_suffix = '.valgrind.log'
cls.valgrind = Path(os.getenv("VALGRIND"))
cls.valgrind_args = f'-v --trace-children=yes --track-fds=yes --leak-check=full ' \
f'--suppressions={cls.path_source / "unit_tests" / "valgrind.supp"} ' \
f'--log-file={cls.path_tmp / "valgrind.log"} ' \
f'--error-exitcode=123'
# cls.log.info(f"{cls.__name__} Environment:")
# cls.log.info(f" version: {cls.version}")
# cls.log.info(f" path_source: {cls.path_source}")
# cls.log.info(f" path_build: {cls.path_build}")
# cls.log.info(f" path_tmp: {cls.path_tmp}")
# cls.log.info(f" check_clamav: {cls.check_clamav}")
# cls.log.info(f" check_clamd: {cls.check_clamd}")
# cls.log.info(f" check_fpu_endian: {cls.check_fpu_endian}")
# cls.log.info(f" milter: {cls.milter}")
# cls.log.info(f" clambc: {cls.clambc}")
# cls.log.info(f" clamd: {cls.clamd}")
# cls.log.info(f" clamdscan: {cls.clamdscan}")
# cls.log.info(f" clamdtop: {cls.clamdtop}")
# cls.log.info(f" clamscan: {cls.clamscan}")
# cls.log.info(f" clamsubmit: {cls.clamsubmit}")
# cls.log.info(f" clamconf: {cls.clamconf}")
# cls.log.info(f" clamonacc: {cls.clamonacc}")
# cls.log.info(f" freshclam: {cls.freshclam}")
# cls.log.info(f" sigtool: {cls.sigtool}")
# cls.log.info(f" valgrind: {cls.valgrind}")
@classmethod
def tearDownClass(cls):
"""
Clean up after ourselves,
Delete the generated tmp directory.
"""
print("")
try:
shutil.rmtree(cls.path_tmp)
cls.log.info("Removed tmp directory: {}".format(cls.path_tmp))
except Exception:
cls.log.info("No tmp directory to clean up.")
def setUp(self):
print("")
log_path = Path(self.path_build / 'unit_tests' / f'{self._testMethodName}{self.log_suffix}')
log_path.unlink(missing_ok=True)
self.log = Logger(self._testMethodName, log_file=str(log_path))
def tearDown(self):
print("")
def step_name(self, name):
"""Log name of a step.
:Parameters:
- `name`: a string with name of the step to print.
"""
self.log.info("~" * 72)
self.log.info(name.center(72, " "))
self.log.info("~" * 72)
def execute(self, cmd, cwd=None, **kwargs):
"""Execute command.
This method composes shell command from passed args and executes it.
Command template: '[sudo] cmd [options] data'
Example:
cmd='cp', data='source_file dest_file', options=['r','f'],
sudo=True
Composed result: 'sudo cp -rf source_file dest_file'.
:Parameters:
- `cmd`: a string with a shell command to execute.
- `cwd`: a string with a current working directory to set.
:Keywords:
- `data`: args for `cmd`(e.g. filename, dirname,).
- `options`: options for the shell command.
- `sudo`: use `sudo`? Default value is False.
- `timeout`: execution timeout in seconds.
- `env_vars`: a dictionary with custom environment variables.
- `interact`: a string to enter to the command stdin during
execution.
:Return:
- namedtuple(ec, out, err).
:Exceptions:
- `AssertionError`: is raised if `options` is not a list.
"""
executor = Executor(logger=self.log)
return executor.execute(cmd, cwd=cwd, kwargs=kwargs)
def verify_output(self, text, expected=[], unexpected=[], order=ANY_ORDER):
"""Method verifies text. Check for expected or unexpected results.
:Parameters:
- `text`: text to verify.
- `expected`: (iterable) expected items to be found.
- `unexpected`: (iterable) unexpected items to be found.
- `order`: expected appearance order. Default: any order.
"""
log_checker = LogChecker(self.log)
if unexpected:
log_checker.verify_unexpected_output(unexpected, text)
if expected:
log_checker.verify_expected_output(expected, text, order=order)
def verify_log(
self, log_file, expected=[], unexpected=[], ignored=[], order=ANY_ORDER
):
"""Method verifies log file. Check for expected or unexpected results.
:Parameters:
- `log_file`: path to log file.
- `expected`: (iterable) expected items to be found.
- `unexpected`: (iterable) unexpected items to be found.
- `ignored`: (iterable) unexpected items which should be ignored.
- `order`: expected appearance order. Default: any order.
"""
log_checker = LogChecker(self.log)
if unexpected:
log_checker.verify_unexpected_log(log_file, unexpected=unexpected, ignored=ignored)
if expected:
log_checker.verify_expected_log(log_file, expected=expected, order=order)
def verify_valgrind_log(self, log_file: Union[Path, None]=None):
"""Method verifies a valgrind log file.
If valgrind not enabled this is basically a nop.
:Parameters:
- `log_file`: path to log file.
"""
if self.valgrind == "":
return
if log_file == None:
log_file = self.path_tmp / 'valgrind.log'
if not log_file.exists():
raise AssertionError(f'{log_file} not found. Valgrind failed to run?')
errors = False
self.log.info(f'Verifying {log_file}...')
try:
self.verify_log(
str(log_file),
expected=['ERROR SUMMARY: 0 errors'],
unexpected=[],
ignored=[]
)
except AssertionError:
self.log.warning("*" * 69)
self.log.warning(f'Valgrind test failed!'.center(69, ' '))
self.log.warning(f'Please submit this log to https://bugzilla.clamav.net:'.center(69, ' '))
self.log.warning(f'{log_file}'.center(69, ' '))
self.log.warning("*" * 69)
errors = True
finally:
with log_file.open('r') as log:
found_summary = False
for line in log.readlines():
if 'ERROR SUMMARY' in line:
found_summary = True
if (found_summary or errors) and len(line) < 500:
self.log.info(line.rstrip('\n'))
if errors:
raise AssertionError('Valgrind test FAILED!')
def verify_cmd_result(
self,
result,
exit_code=0,
stderr_expected=[],
stderr_unexpected=[],
stdout_expected=[],
stdout_unexpected=[],
order=ANY_ORDER,
):
"""Check command result for expected/unexpected stdout/stderr.
:Parameters:
- `result`: tuple(ec, out, err).
- `exit_code`: expected exit code value.
- `stderr_expected`: (iterable) expected items in stderr.
- `stderr_unexpected`: (iterable) unexpected items in stderr.
- `stdout_expected`: (iterable) expected items in stdout.
- `stdout_unexpected`: (iterable) unexpected items in stdout.
- `order`: expected appearance order. Default: any order.
:Exceptions:
- `AssertionError`: is raised if:
1) format of `result` is wrong.
2) actual exit code value doesn't match expected.
"""
try:
ec, stdout, stderr = result
except:
raise AssertionError("Wrong result format: %s" % (result,))
assert ec == exit_code, (
"Code mismatch.\nExpected: %s\nActual: %s\nError: %s"
% (exit_code, ec, stderr)
)
if stderr_expected:
self.verify_expected_output(
stderr_expected, stderr, order=order
)
if stderr_unexpected:
self.verify_unexpected_output(stderr_unexpected, stderr)
if stdout_expected:
self.verify_expected_output(
stdout_expected, stdout, order=order
)
if stdout_unexpected:
self.verify_unexpected_output(stdout_unexpected, stdout)
def _md5(self, filepath):
"""Get md5 hash sum of a given file.
:Parameters:
- `filepath`: path to file.
:Return:
- hash string
:Exceptions:
- `AssertionError`: is raised if `filepath` is not a string
or is empty.
"""
assert isinstance(filepath, str), "Invalid filepath: %s." % (filepath,)
assert os.path.exists(filepath), "file does not exist: %s." % (filepath,)
hash_md5 = hashlib.md5()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()
def get_md5(self, files):
"""Get md5 hash sum of every given file.
:Parameters:
- `files`: a list or a tuple of files.
:Return:
- dictionary like {file: md5sum}.
:Exceptions:
- `AssertionError`: is raised if `files` is empty.
"""
assert files, "`files` should not be empty."
files = files if isinstance(files, (list, tuple)) else [files]
md5_dict = {}
for path in files:
if os.path.isfile(path):
md5_dict[path] = self._md5(path)
return md5_dict
def _pkill(self, process, options=["-9 -f"], sudo=False):
"""Wrapper for CLI *nix `pkill` command.
*nix only.
:Parameters:
- `process`: a string with pattern for process to kill.
- `options`: options for `pkill` command.
- `sudo`: use `sudo`? Default value is False.
:Return:
- namedtuple(ec, out, err).
:Exceptions:
- `AssertionError`: is raised if `process` is empty or is
not a string.
"""
assert self.operating_system != "windows"
assert (
isinstance(process, str) and process
), "`process` must be a non-empty string."
result = ""
error = ""
code = None
res = self.execute(
"pkill", data='"%s"' % (process,), options=options, sudo=sudo
)
if res.ec != 0:
self.log.warning("Failed to pkill `%s` process." % (process,))
code, error, result = (
res.ec if not code or code == 0 else code,
"\n".join([error, res.err]),
"\n".join([result, res.out]),
)
return namedtuple("CmdResult", ["ec", "out", "err"])(code, result, error)
def _taskkill(self, process, match_all=True):
"""Stop processes matching the given name.
Windows only.
:Parameters:
- `processes`: process name.
- `match_all`: find all processes that match 'process'.
:Return:
- namedtuple(ec, out, err).
:Exceptions:
- `AssertionError`: is raised if:
1) `processes` is not a string or is an empty string.
"""
assert self.operating_system == "windows"
wildcard = "*" if match_all else ""
result = ""
error = ""
code = None
res = self.execute('taskkill /F /IM "%s%s"' % (process, wildcard))
if res.ec != 0:
self.log.error("Failed to `stop` process.\nError: %s." % (res.err,))
code, error, result = (
res.ec if not code or code == 0 else code,
"\n".join([error, res.err]),
"\n".join([result, res.out]),
)
return namedtuple("CmdResult", ["ec", "out", "err"])(code, result, error)
def stop_process(self, processes, options=["-9 -f"], sudo=False):
"""Stop all specified processes.
:Parameters:
- `processes`: string name of a process, or a list or a tuple of processes to stop.
- `match_all`: find all processes that match 'processes'.
:Return:
- namedtuple(ec, out, err).
:Exceptions:
- `AssertionError`: is raised if:
1) `processes` is not a string or is an empty string.
"""
assert processes, "`processes` should not be empty."
processes = processes if isinstance(processes, (list, tuple)) else [processes]
results = []
for process in processes:
if self.operating_system == "windows":
res = self._taskkill(process, match_all=True)
else:
res = self._pkill(process, options, sudo)
results.append(res)
return results
def execute_command(self, cmd, **kwargs):
"""Execute custom command.
:Return:
- namedtuple(ec, out, err).
"""
return self.execute(cmd, **kwargs)
class Logger(object):
"""Logger class."""
_format = "[%(levelname)s]: %(message)s"
_level = logging.DEBUG
levels = {
"debug": logging.DEBUG,
"info": logging.INFO,
"warning": logging.WARNING,
"error": logging.ERROR,
"critical": logging.CRITICAL,
}
def __init__(self, name, level="debug", log_file=""):
"""Initialize Logger instance."""
self.core = logging.getLogger(name)
self.core.propagate = False
self.set_level(level)
formatter = logging.Formatter(self._format, "%Y-%m-%d %H:%M:%S")
try:
handler = logging.StreamHandler(strm=sys.stdout)
except TypeError:
handler = logging.StreamHandler(stream=sys.stdout)
finally:
handler.setFormatter(formatter)
self.core.addHandler(handler)
if log_file != "":
filehandler = logging.FileHandler(filename=log_file)
filehandler.setLevel(self.levels[level.lower()])
filehandler.setFormatter(formatter)
self.core.addHandler(filehandler)
def set_level(self, level):
"""Set logging level."""
self.core.setLevel(self.levels[level.lower()])
def __getattr__(self, attr):
return getattr(self.core, attr)
class Executor(object):
"""Common CLI executor class."""
def __init__(self, logger=None):
"""Initialize BaseExecutor instance."""
global loggers
if logger != None:
self._logger = logger
else:
if loggers.get(self.__class__.__name__):
self._logger = loggers.get(self.__class__.__name__)
else:
self._logger = Logger(self.__class__.__name__)
loggers[self.__class__.__name__] = self._logger
self._process = None
self._process_pid = None
self.result = None
self.error = None
self.code = None
self.terminated = False
def _log_cmd_results(self):
"""Log exit code, stdout and stderr of the executed command."""
self._logger.debug("Exit code: %s" % self.code)
self._logger.debug("stdout: %s" % self.result)
if self.code:
self._logger.debug("stderr: %s" % self.error)
def _start_cmd_thread(self, target, target_args, timeout=EXECUTION_TIMEOUT):
"""Start command thread and kill it if timeout exceeds.
:Return:
- namedtuple(ec, out, err).
"""
# Start monitor thread.
thread = threading.Thread(target=target, args=target_args)
thread.start()
thread.join(timeout)
# Kill process if timeout exceeded.
if thread.is_alive():
if platform.system() == "Windows":
os.kill(self._process_pid, signal.CTRL_C_EVENT)
else:
os.killpg(self._process_pid, signal.SIGTERM)
self.terminated = True
thread.join()
return namedtuple("CmdResult", ["ec", "out", "err"])(
self.code, self.result, self.error
)
def __run(self, cmd, cwd=None, env_vars={}, interact=""):
"""Execute command in separate thread."""
if platform.system() == "Windows":
self._logger.debug("Run command: %s" % (cmd,))
self._process = subprocess.Popen(
cmd,
cwd=cwd,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
)
else:
sys_env = os.environ.copy()
sys_env.update(env_vars)
self._logger.debug("Run command: %s" % (cmd,))
self._process = subprocess.Popen(
cmd,
cwd=cwd,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
preexec_fn=os.setsid,
env=sys_env,
shell=True,
)
self._process_pid = self._process.pid
self.result, self.error = self._process.communicate(interact)
if self.result != None:
self.result = self.result.decode("utf-8", "ignore")
self.error = self.error.decode("utf-8", "ignore")
self.code = self._process.returncode
if self.terminated:
self.error = 'Execution timeout exceeded for "%s" command.' % (cmd,)
self.code = TIMEOUT_EXIT_CODE
self.terminated = False
self._log_cmd_results()
def execute(self, cmd, cwd=None, **kwargs):
"""Execute command.
This method composes shell command from passed args and executes it.
Command template: '[sudo] cmd [options] data'
Example:
cmd='cp', data='source_file dest_file', options=['r','f'],
sudo=True
Composed result: 'sudo cp -rf source_file dest_file'.
:Parameters:
- `cmd`: a string with a shell command to execute.
- `cwd`: a string with a current working directory to set.
:Keywords:
- `data`: args for `cmd`(e.g. filename, dirname,).
- `options`: options for the shell command.
- `sudo`: use `sudo`? Default value is False.
- `timeout`: execution timeout in seconds.
- `env_vars`: a dictionary with custom environment variables.
- `interact`: a string to enter to the command stdin during
execution.
:Return:
- namedtuple(ec, out, err).
:Exceptions:
- `AssertionError`: is raised if `options` is not a list.
"""
data = kwargs.get("data", "")
options = kwargs.get("options", [])
sudo = kwargs.get("sudo", False)
timeout = int(kwargs.get("timeout") or EXECUTION_TIMEOUT)
env_vars = kwargs.get("env_vars", {})
interact = kwargs.get("interact", "")
assert isinstance(options, list), "`options` must be a list."
if platform.system() == "Windows":
timeout = EXECUTION_TIMEOUT
return self._start_cmd_thread(self.__run, (cmd, cwd, interact), timeout)
else:
opts = ""
if options:
# Remove duplicates preserving the order:
unq_opts = []
for option in options:
option = option.strip("- ")
if option not in unq_opts:
unq_opts.append(option)
opts = "-%s " % ("".join(unq_opts),)
# Build command.
execute_cmd = "%s %s%s" % (cmd, opts, data)
if sudo:
execute_cmd = "sudo %s" % (execute_cmd,)
return self._start_cmd_thread(
self.__run, (execute_cmd, cwd, env_vars, interact), timeout
)
class LogChecker:
"""This class provides methods to check logs and strings."""
def __init__(self, logger=None):
"""Initialize LogChecker instance."""
global loggers
if logger != None:
self._logger = logger
else:
if loggers.get(self.__class__.__name__):
self._logger = loggers.get(self.__class__.__name__)
else:
self._logger = Logger(self.__class__.__name__)
loggers[self.__class__.__name__] = self._logger
@staticmethod
def _prepare_value(value):
"""Convert given value to a list if needed."""
return value if isinstance(value, (tuple, list)) else [value]
def __crop_output(self, output, limit=(2000, 2000)):
"""Crop string with output to specified limits.
:Parameters:
- `output`: a string to be cropped.
- `limit`: a tuple with a range to be cropped from `output`.
:Return:
- cropped `output` if its length exceeds limit, otherwise -
`output`.
"""
crop_message = (
""
if len(output) <= sum(limit)
else "\n\n----- CROPPED -----\n ...\n----- CROPPED -----\n\n"
)
if crop_message:
return "".join((output[: limit[0]], crop_message, output[-limit[1] :]))
return output
def verify_expected_output(self, expected_items, output, order=STRICT_ORDER):
"""Check presence of regex patterns in output string.
:Parameters:
- `expected_items`: a list of regex patterns that should be found
in `output`.
- `output`: a string with output to verify.
- `order`: STRICT_ORDER, ANY_ORDER.
:Exceptions:
- `AssertionError`: is raised if:
1)`output` is not a string.
2) one of expected items was not found in `output`.
3) items were found in wrong order.
"""
if output != None and not isinstance(output, str):
output = output.decode("utf-8", "ignore")
assert isinstance(output, str), "`output` must be a string."
expected_items = self._prepare_value(expected_items)
last_found_position = 0
for item in expected_items:
pattern = re.compile(item)
match = pattern.search(output)
assert match, "Expected item `%s` not found in output:\n%s" % (
item,
self.__crop_output(output),
)
current_found_position = match.start()
# Compare current found position with last found position
if order == STRICT_ORDER:
assert current_found_position >= last_found_position, (
"Expected item `%s` order is wrong in output:\n%s"
% (item, self.__crop_output(output))
)
last_found_position = current_found_position
def verify_unexpected_output(self, unexpected_items, output):
"""Check absence of regex patterns in output string.
:Parameters:
- `unexpected_items`: a list of regex patterns that should be
absent in `output`.
- `output`: a string with output to verify.
:Exceptions:
- `AssertionError`: is raised if:
1)`output` is not a string.
2) one of unexpected items was found in `output`.
"""
if output != None and not isinstance(output, str):
output = output.decode("utf-8", "ignore")
assert isinstance(output, str), "`output` must be a string."
unexpected_items = self._prepare_value(unexpected_items)
for item in unexpected_items:
pattern = re.compile(item)
match = pattern.search(output)
assert not match, (
"Unexpected item `%s` which should be absent "
"found in output:\n%s" % (item, self.__crop_output(output))
)
def verify_expected_log(self, filename, expected=[], order=STRICT_ORDER):
"""Check presence of regex patterns in specified file.
:Parameters:
- `filename`: a string with absolute path to a file.
- `expected`: a list of regex patterns that should be found in
the file.
- `order`: STRICT_ORDER, ANY_ORDER.
:Exceptions:
- `AssertionError`: is raised if:
1)`filename` is not a string.
2) specified file doesn't exist.
3) one of expected items was not found in the file.
4) items were found in wrong order.
"""
if filename != None and not isinstance(filename, str):
filename = filename.decode("utf-8", "ignore")
assert isinstance(filename, str), "`filename` must be a string."
assert os.path.isfile(filename), "No such file: %s." % (filename,)
expected = self._prepare_value(expected)
def read_log():
"""Read log file in chunks."""
with open(filename, "r") as file_reader:
prev_lines, lines = [], []
for idx, line in enumerate(file_reader, 1):
lines.append(line)
if idx % CHUNK_SIZE == 0:
yield idx, "".join(prev_lines + lines)
prev_lines, lines = lines, []
if lines:
yield idx, "".join(prev_lines + lines)
results = {}
for line_idx, chunk in read_log():
chunk_size = chunk.count("\n")
for item in expected:
matches_iterator = re.finditer(
r"%s" % (item,), chunk, flags=re.MULTILINE
)
for match in matches_iterator:
relative_line = chunk.count("\n", 0, match.start()) + 1
line = max(relative_line, line_idx - chunk_size + relative_line)
results[item] = results.get(item, [line])
if line not in results[item]:
results[item].append(line)
if order == STRICT_ORDER:
last_found_position = 0
for item in expected:
found_matches = results.get(item)
assert found_matches, "Expected item `%s` not found in " "file: %s." % (
item,
filename,
)
if len(found_matches) > 1:
self._logger.warning("More than one match for item `%s`." % (item,))
# Item(s) found. Let's get line number of first appearance.
current_found_position = found_matches[0]
# Compare first appearances of current and previous items.
assert current_found_position > last_found_position, (
"Expected item `%s` order is wrong in file: %s.\n"
"Current position: %s.\nPrevious position: %s."
% (item, filename, current_found_position, last_found_position)
)
last_found_position = current_found_position
else:
for item in expected:
found_matches = results.get(item)
assert found_matches, "Expected item `%s` not found in " "file: %s." % (
item,
filename,
)
if len(found_matches) > 1:
self._logger.warning("More than one match for item `%s`." % (item,))
def verify_unexpected_log(self, filename, unexpected=[], ignored=[]):
"""Check absence of regex patterns in specified file.
:Parameters:
- `filename`: a string with absolute path to a file.
- `unexpected`: a list of regex patterns that should be absent in
the file.
- `ignored`: a list of regex patterns that should be ignored.
:Exceptions:
- `AssertionError`: is raised if:
1)`filename` is not a string.
2) specified file doesn't exist.
3) one of unexpected items was found in the file.
"""
if filename != None and not isinstance(filename, str):
filename = filename.decode("utf-8", "ignore")
assert isinstance(filename, str), "`filename` must be a string."
assert os.path.isfile(filename), "No such file: %s." % (filename,)
unexpected = self._prepare_value(unexpected)
ignored = self._prepare_value(ignored)
with open(filename, "r") as file_reader:
found_items = []
for line in file_reader:
for item in unexpected:
if re.search(r"%s" % (item,), line):
found_items.append(line.strip())
if ignored:
for item in ignored:
for line in found_items[:]:
if re.search(r"%s" % (item,), line):
found_items.remove(line)
assert len(found_items) == 0, "Unexpected items were found in %s:\n%s" % (
filename,
found_items,
)

@ -1,10 +1,11 @@
# Copyright (C) 2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
cmake_minimum_required( VERSION 3.12...3.13 )
if(WIN32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DHAVE_STRUCT_TIMESPEC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
endif()
# The win32_compat object library

Loading…
Cancel
Save