From 6cefaa81bb80da37d38afc2d52b91703f13aa249 Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 3 Nov 2025 07:40:56 -0500 Subject: [PATCH 01/28] Maj core & libssh2 --- CMakeLists.txt | 23 +++++++++++++++++++++++ core | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c56814..2e5bcff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,9 @@ project(C2TeamServer VERSION 0.0.0 LANGUAGES CXX C) set(CMAKE_BUILD_TYPE Release) set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + ## ## Conan Dependencies @@ -21,6 +24,26 @@ find_package(httplib REQUIRED) include_directories(${CMAKE_INCLUDE_PATH}) +## +## Fetch for core +## + +include(FetchContent) + +set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared libraries" FORCE) +set(BUILD_STATIC_LIBS ON CACHE BOOL "Build static libraries" FORCE) +set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +set(BUILD_TESTING OFF CACHE BOOL "" FORCE) + +FetchContent_Declare( + libssh2 + GIT_REPOSITORY https://github.com/libssh2/libssh2.git + GIT_TAG libssh2-1.11.1 +) + +FetchContent_MakeAvailable(libssh2) + + ## ## Config Tests et Logs ## diff --git a/core b/core index 2f6652d..bd7e71a 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 2f6652ddbb70378c187e06d64285175f2f5cfcfb +Subproject commit bd7e71a814d47ccaaa6ab427e90eb4bf850f8cc5 From e8ee13495da6053f566072b8fb3333ce33752003 Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Tue, 6 Jan 2026 10:24:42 -0500 Subject: [PATCH 02/28] fix libSocks5 --- libs/libSocks5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/libSocks5 b/libs/libSocks5 index 1c6777a..8ef9dd3 160000 --- a/libs/libSocks5 +++ b/libs/libSocks5 @@ -1 +1 @@ -Subproject commit 1c6777a2388e517c6fc2193a7b2f6897ade8e7cb +Subproject commit 8ef9dd3549ca1a06000628ea6be6dcc3935a9969 From 363ca20abb46cba4fed2ae443b7a32f17727db03 Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Tue, 6 Jan 2026 10:28:34 -0500 Subject: [PATCH 03/28] Add websocket support for http listener & beacon --- CMakeLists.txt | 1 + conanfile.txt | 1 + core | 2 +- teamServer/CMakeLists.txt | 2 +- teamServer/teamServer/TeamServerConfig.json | 10 ++++++++++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e5bcff..4487125 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ find_package(protobuf REQUIRED) find_package(ZLIB REQUIRED) find_package(spdlog REQUIRED) find_package(httplib REQUIRED) +find_package(Crow REQUIRED) include_directories(${CMAKE_INCLUDE_PATH}) diff --git a/conanfile.txt b/conanfile.txt index 4e2fbaa..60102bb 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -4,6 +4,7 @@ protobuf/5.27.0 spdlog/1.15.3 cpp-httplib/0.20.1 openssl/3.5.1 +crowcpp-crow/1.3.0 [layout] cmake_layout diff --git a/core b/core index bd7e71a..712d89d 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit bd7e71a814d47ccaaa6ab427e90eb4bf850f8cc5 +Subproject commit 712d89d8a77445b34b6e5647a46d1c9bb6a97a15 diff --git a/teamServer/CMakeLists.txt b/teamServer/CMakeLists.txt index 01b9b79..180a00e 100644 --- a/teamServer/CMakeLists.txt +++ b/teamServer/CMakeLists.txt @@ -17,7 +17,7 @@ add_executable(TeamServer ${SOURCES_TEAMSERVER}) if(WIN32) target_link_libraries(TeamServer Dnscommunication SocketHandler GrpcMessages openssl::openssl ${OPENSSL_CRYPTO_LIBRARY} ZLIB::ZLIB grpc::grpc spdlog::spdlog SocksServer) else() - target_link_libraries(TeamServer Dnscommunication SocketHandler GrpcMessages pthread openssl::openssl ZLIB::ZLIB grpc::grpc spdlog::spdlog httplib::httplib SocksServer dl rt) + target_link_libraries(TeamServer Dnscommunication SocketHandler GrpcMessages pthread openssl::openssl ZLIB::ZLIB grpc::grpc spdlog::spdlog httplib::httplib SocksServer Crow::Crow dl rt) endif() add_custom_command(TARGET TeamServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy diff --git a/teamServer/teamServer/TeamServerConfig.json b/teamServer/teamServer/TeamServerConfig.json index d933c96..d4ad39e 100644 --- a/teamServer/teamServer/TeamServerConfig.json +++ b/teamServer/teamServer/TeamServerConfig.json @@ -25,6 +25,11 @@ "/MicrosoftUpdate/ShellEx/KB242742/admin.aspx", "/MicrosoftUpdate/ShellEx/KB242742/download.aspx" ], + "wsUri": [ + "/ws1", + "/ws", + "/test/ws" + ], "uriFileDownload": "/images/commun/1.084.4584/serv/", "downloadFolder": "../www", "server": { @@ -46,6 +51,11 @@ "/MicrosoftUpdate/ShellEx/KB242742/upload.aspx", "/MicrosoftUpdate/ShellEx/KB242742/config.aspx" ], + "wsUri": [ + "/ws1", + "/ws", + "/test/ws" + ], "uriFileDownload": "/images/commun/1.084.4584/serv/", "downloadFolder": "../www", "server": { From 21cf0c021ccf6a6dd85ef1f8af17fd30079cf879 Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 12:27:11 +0200 Subject: [PATCH 04/28] Agent for building in wsl --- AGENT.md | 83 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/AGENT.md b/AGENT.md index 7a90556..07e687f 100644 --- a/AGENT.md +++ b/AGENT.md @@ -1,58 +1,67 @@ -# 🧠 AGENT.md +# AGENT.md ## Agent Role -You are an **expert C++ and CMake assistant** dedicated to supporting the C2TeamServer codebase: +You are an expert C++ and CMake assistant working on the C2TeamServer codebase. -* ✅ Fluent with the **project’s existing C++ style, directory layout, and CMake syntax**. -* ⛔ **Do not attempt to build or compile the project**—that process is resource-intensive and time-consuming. -* 🎯 Your focus is on **code understanding, guidance, edits**. +- Match the project's existing C++ style, directory layout, and CMake structure. +- Work directly in the WSL workspace for this repository. +- Validate meaningful changes by compiling and testing the project. ---- +## Build And Test Policy -## 📌 Responsibilities +From now on, the project must be compiled and tested during implementation work when the change can affect build or runtime behavior. -### Code Review & Navigation +- Use WSL paths and run the toolchain from `/home/max/project/C2TeamServer`. +- Prefer a clean dedicated build directory such as `build-codex-scratch` for verification instead of reusing an unknown existing `build/` tree. +- Use Conan for dependencies, CMake for configuration, and GNU Make with GCC for the build. +- After code changes, run at least the project configure step, the build, and `ctest --output-on-failure` when feasible. -* Analyze and explain C++ classes, actions, and CMake configurations. -* Help trace calls from gRPC definitions to implementation. -* Locate where libraries and dependencies are imported and used. +## Validated Environment -### Style & Syntax Alignment +The following toolchain was verified successfully in WSL: -* Provide suggestions strictly following the project's CMake and C++ style conventions (e.g., variable naming, build targets, include directories). -* Maintain consistency with existing module structure and naming. +- `cmake 3.28.3` +- `conan 2.24.0` +- `gcc 13.3.0` +- `make 4.3` -### Documentation & Guidance +## Validated Build Commands -* Generate lightweight helper scripts (e.g. code snippets, CMake snippets, CLI usage). -* Draft small additions to README, comments, or doc files to clarify behavior—without rebuilding. +Run the project from scratch with: -### Troubleshooting and Q\&A +```bash +cd /home/max/project/C2TeamServer +mkdir -p build-codex-scratch +cd build-codex-scratch +cmake .. -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=/home/max/project/C2TeamServer/conan_provider.cmake +make +ctest --output-on-failure +``` -* Troubleshoot code logic, gRPC interactions, and CMake file references. -* Answer developer questions about function behavior, build targets, or directory layout. -* Provide suggestions for refactors, optimizations, or better code organization that aligns with the existing style. +Notes: ---- +- The `README.md` example using `-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./conan_provider.cmake` was not reliable in the validated setup. +- Use the absolute path to `conan_provider.cmake` shown above. +- The validated CTest registration currently executes `testsTestServer`. -## 🚫 What You Should Not Do +## Responsibilities -* 🛠 Attempt to build the TeamServer or its dependencies locally. -* ⚠ Perform any heavy code generation or restructuring that would require a full build. -* 🔁 Initiate or recommend large build automations or CI integrations. +- Analyze and explain C++ classes, actions, and CMake configuration. +- Trace behavior from gRPC definitions to implementation. +- Keep edits consistent with the existing module structure and naming. +- Update documentation when build or workflow details change. +- Verify that code changes still configure, compile, and test correctly. ---- +## Working Rules -## ✅ Summary +- Do not assume the existing `build/` directory is clean. +- Do not skip compilation for implementation tasks unless the user explicitly asks for analysis only or the environment blocks execution. +- If build or test execution fails, report the exact failing step and reason. +- Prefer precise, minimal changes that preserve the current architecture. -| Role | Description | -| --------------------- | ---------------------------------------------------------------------------- | -| **Expert Agent** | Deep knowledge of C++17 and CMake for command-and-control code | -| **No Builds** | Don’t compile the project or port it to other systems; skip heavy operations | -| **Style-Focused** | Always match project's existing syntax and modular layout | -| **Lightweight Tasks** | Commentary, documentation, small code reviews, snippet generation | +## Summary ---- - -You are effectively the **senior C++/CMake co-pilot** for the project—always aligned with the existing style, focused on clarity and precision, and avoiding resource-intensive operations. +- Primary role: senior C++/CMake copilot for C2TeamServer. +- Required workflow: inspect, edit, compile, test, then report. +- Verified build path: WSL + Conan + CMake + Make + GCC. From 4bbfc3ceb2ce8a81da32024b8677913738f07b46 Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 12:55:35 +0200 Subject: [PATCH 05/28] Stabilize build and test foundation --- .gitignore | 2 ++ CMakeLists.txt | 51 ++++++++++++++++++++++++++++----------- certs/CMakeLists.txt | 20 +++++++-------- core | 2 +- libs/CMakeLists.txt | 6 ++--- teamServer/CMakeLists.txt | 12 ++++----- 6 files changed, 58 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index e6b6685..2434b35 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,10 @@ Tests C2Client/build/ .vscode build/ +build-*/ C2Client/.cmdHistory C2Client/.termHistory +C2Client/.venv/ C2Client/Beacon.exe C2Client/C2Client/Scripts/__init__.py C2Client/C2Client/TerminalModules/__pycache__/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 4487125..8156079 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,27 @@ cmake_minimum_required(VERSION 3.24.0 FATAL_ERROR) -project(C2TeamServer VERSION 0.0.0 LANGUAGES CXX C) -set(CMAKE_BUILD_TYPE Release) -set(CMAKE_CXX_STANDARD 17) - -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) +project(C2TeamServer VERSION 0.0.0 LANGUAGES CXX C) +set(CMAKE_BUILD_TYPE Release) +set(CMAKE_CXX_STANDARD 17) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +set(C2_BUILD_ARTIFACT_ROOT "${CMAKE_BINARY_DIR}/artifacts") +set(C2_RUNTIME_ROOT "${C2_BUILD_ARTIFACT_ROOT}/Release") +set(C2_TEAMSERVER_RUNTIME_OUTPUT_DIR "${C2_RUNTIME_ROOT}/TeamServer") +set(C2_RUNTIME_MODULE_OUTPUT_DIR "${C2_RUNTIME_ROOT}/TeamServerModules") +set(C2_TEST_BIN_OUTPUT_DIR "${CMAKE_BINARY_DIR}/tests/bin") +set(C2_GENERATED_PYTHON_GRPC_DIR "${CMAKE_BINARY_DIR}/generated/python_grpc") +set(C2_BASE64_SOURCE_DIR "${CMAKE_SOURCE_DIR}/thirdParty/base64") +set(C2_DONUT_SOURCE_DIR "${CMAKE_SOURCE_DIR}/thirdParty/donut") + +file(MAKE_DIRECTORY + "${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}" + "${C2_RUNTIME_MODULE_OUTPUT_DIR}" + "${C2_TEST_BIN_OUTPUT_DIR}" + "${C2_GENERATED_PYTHON_GRPC_DIR}" +) ## @@ -49,14 +65,21 @@ FetchContent_MakeAvailable(libssh2) ## Config Tests et Logs ## -option(WITH_TESTS "Compile for tests" ON) - -option(BUILD_TEAMSERVER "Enable Teamserver config" ON) -add_definitions(-DBUILD_TEAMSERVER) - -if(WITH_TESTS) - set(SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG) -endif() +option(WITH_TESTS "Compile for tests" ON) +set(C2CORE_BUILD_TESTS ${WITH_TESTS}) + +option(BUILD_TEAMSERVER "Enable Teamserver config" ON) +add_definitions(-DBUILD_TEAMSERVER) + +if(WITH_TESTS) + set(SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG) + FetchContent_Declare( + nlohmann_json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.12.0 + ) + FetchContent_MakeAvailable(nlohmann_json) +endif() ## diff --git a/certs/CMakeLists.txt b/certs/CMakeLists.txt index 35244a0..d643d9c 100644 --- a/certs/CMakeLists.txt +++ b/certs/CMakeLists.txt @@ -5,11 +5,9 @@ else() execute_process(COMMAND bash -c "cd ${CMAKE_BINARY_DIR}/certs/sslBeaconHttps && ./genSslCert.sh localhost") - file(COPY ${CMAKE_BINARY_DIR}/certs/sslBeaconHttps/localhost.key DESTINATION ${CMAKE_SOURCE_DIR}/Release/TeamServer/) - #file(RENAME ${CMAKE_SOURCE_DIR}/Release/TeamServer/localhost.key ${CMAKE_SOURCE_DIR}/Release/TeamServer/localhost.key) + file(COPY ${CMAKE_BINARY_DIR}/certs/sslBeaconHttps/localhost.key DESTINATION ${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/) - file(COPY ${CMAKE_BINARY_DIR}/certs/sslBeaconHttps/localhost.crt DESTINATION ${CMAKE_SOURCE_DIR}/Release/TeamServer/) - #file(RENAME ${CMAKE_SOURCE_DIR}/Release/TeamServer/localhost.crt ${CMAKE_SOURCE_DIR}/Release/TeamServer/localhost.crt) + file(COPY ${CMAKE_BINARY_DIR}/certs/sslBeaconHttps/localhost.crt DESTINATION ${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/) endif() @@ -21,14 +19,14 @@ else() execute_process(COMMAND bash -c "cd ${CMAKE_BINARY_DIR}/certs/sslTeamServ && ./genSslCert.sh") # server.key - file(COPY ${CMAKE_BINARY_DIR}/certs/sslTeamServ/server-key.pem DESTINATION ${CMAKE_SOURCE_DIR}/Release/TeamServer/) - file(RENAME ${CMAKE_SOURCE_DIR}/Release/TeamServer/server-key.pem ${CMAKE_SOURCE_DIR}/Release/TeamServer/server.key) + file(COPY ${CMAKE_BINARY_DIR}/certs/sslTeamServ/server-key.pem DESTINATION ${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/) + file(RENAME ${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/server-key.pem ${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/server.key) # server.crt - file(COPY ${CMAKE_BINARY_DIR}/certs/sslTeamServ/server.pem DESTINATION ${CMAKE_SOURCE_DIR}/Release/TeamServer/) - file(RENAME ${CMAKE_SOURCE_DIR}/Release/TeamServer/server.pem ${CMAKE_SOURCE_DIR}/Release/TeamServer/server.crt) + file(COPY ${CMAKE_BINARY_DIR}/certs/sslTeamServ/server.pem DESTINATION ${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/) + file(RENAME ${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/server.pem ${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/server.crt) # rootCA.crt - file(COPY ${CMAKE_BINARY_DIR}/certs/sslTeamServ/ca.pem DESTINATION ${CMAKE_SOURCE_DIR}/Release/TeamServer/) - file(RENAME ${CMAKE_SOURCE_DIR}/Release/TeamServer/ca.pem ${CMAKE_SOURCE_DIR}/Release/TeamServer/rootCA.crt) -endif() \ No newline at end of file + file(COPY ${CMAKE_BINARY_DIR}/certs/sslTeamServ/ca.pem DESTINATION ${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/) + file(RENAME ${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/ca.pem ${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/rootCA.crt) +endif() diff --git a/core b/core index 712d89d..18cf32f 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 712d89d8a77445b34b6e5647a46d1c9bb6a97a15 +Subproject commit 18cf32fefdacc0a8079817a24c753e4c42fcccc1 diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index ad9028f..cab0cb8 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -22,8 +22,8 @@ endif() execute_process( COMMAND ${Protobuf_PROTOC_EXECUTABLE} -I=${CMAKE_SOURCE_DIR}/libs/libGrpcMessages/src/ - --grpc_out=${CMAKE_SOURCE_DIR}/C2Client/C2Client/libGrpcMessages/build/py - --python_out=${CMAKE_SOURCE_DIR}/C2Client/C2Client/libGrpcMessages/build/py + --grpc_out=${C2_GENERATED_PYTHON_GRPC_DIR} + --python_out=${C2_GENERATED_PYTHON_GRPC_DIR} --plugin=protoc-gen-grpc=${GRPC_PYTHON_PLUGIN_PROGRAM} ${PROTO_FILES} RESULT_VARIABLE ret2 @@ -41,4 +41,4 @@ add_subdirectory(libDns) add_subdirectory(libMemoryModuleDumy) add_subdirectory(libPipeHandlerDumy) -add_subdirectory(libSocks5) \ No newline at end of file +add_subdirectory(libSocks5) diff --git a/teamServer/CMakeLists.txt b/teamServer/CMakeLists.txt index 180a00e..757a827 100644 --- a/teamServer/CMakeLists.txt +++ b/teamServer/CMakeLists.txt @@ -21,18 +21,18 @@ else() endif() add_custom_command(TARGET TeamServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy -$ "${CMAKE_SOURCE_DIR}/Release/TeamServer/$") +$ "${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/$") add_custom_command(TARGET TeamServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy -${CMAKE_SOURCE_DIR}/teamServer/teamServer/TeamServerConfig.json "${CMAKE_SOURCE_DIR}/Release/TeamServer/TeamServerConfig.json") +${CMAKE_SOURCE_DIR}/teamServer/teamServer/TeamServerConfig.json "${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/TeamServerConfig.json") add_custom_command(TARGET TeamServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy -${CMAKE_SOURCE_DIR}/teamServer/teamServer/auth_credentials.json "${CMAKE_SOURCE_DIR}/Release/TeamServer/auth_credentials.json") +${CMAKE_SOURCE_DIR}/teamServer/teamServer/auth_credentials.json "${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/auth_credentials.json") if(WITH_TESTS) add_executable(testsTestServer tests/testsTestServer.cpp ) target_link_libraries(testsTestServer ) add_custom_command(TARGET testsTestServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy - $ "${CMAKE_SOURCE_DIR}/Tests/$") + $ "${C2_TEST_BIN_OUTPUT_DIR}/$") - add_test(NAME testsTestServer COMMAND "${CMAKE_SOURCE_DIR}/Tests/$") -endif() \ No newline at end of file + add_test(NAME testsTestServer COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") +endif() From 75aee2fb0dcaa4c61ea56da6bf76414f3215eba2 Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 13:25:53 +0200 Subject: [PATCH 06/28] Extract protocol build from libs --- .gitignore | 2 + C2Client/C2Client/grpcClient.py | 10 ++-- C2Client/C2Client/libGrpcMessages/__init__.py | 1 + CMakeLists.txt | 11 ++--- core | 2 +- libs/CMakeLists.txt | 37 -------------- libs/libGrpcMessages/CMakeLists.txt | 3 -- libs/libGrpcMessages/build/cpp/CMakeLists.txt | 18 ------- libs/libGrpcMessages/build/cpp/src/.gitignore | 3 -- protocol/CMakeLists.txt | 49 +++++++++++++++++++ .../src => protocol}/TeamServerApi.proto | 22 ++++----- 11 files changed, 74 insertions(+), 84 deletions(-) create mode 100644 C2Client/C2Client/libGrpcMessages/__init__.py delete mode 100644 libs/libGrpcMessages/CMakeLists.txt delete mode 100644 libs/libGrpcMessages/build/cpp/CMakeLists.txt delete mode 100644 libs/libGrpcMessages/build/cpp/src/.gitignore create mode 100644 protocol/CMakeLists.txt rename {libs/libGrpcMessages/src => protocol}/TeamServerApi.proto (87%) diff --git a/.gitignore b/.gitignore index 2434b35..c61eb1d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ build-*/ C2Client/.cmdHistory C2Client/.termHistory C2Client/.venv/ +__pycache__/ +.pytest_cache/ C2Client/Beacon.exe C2Client/C2Client/Scripts/__init__.py C2Client/C2Client/TerminalModules/__pycache__/ diff --git a/C2Client/C2Client/grpcClient.py b/C2Client/C2Client/grpcClient.py index c937f23..2d9144b 100644 --- a/C2Client/C2Client/grpcClient.py +++ b/C2Client/C2Client/grpcClient.py @@ -11,11 +11,12 @@ import uuid from typing import Any, Iterable, List, Tuple, Optional -sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/libGrpcMessages/build/py/') - import grpc -import TeamServerApi_pb2 -import TeamServerApi_pb2_grpc +from .libGrpcMessages.build.py import TeamServerApi_pb2 + +sys.modules.setdefault("TeamServerApi_pb2", TeamServerApi_pb2) + +from .libGrpcMessages.build.py import TeamServerApi_pb2_grpc MetadataType = List[Tuple[str, str]] @@ -216,4 +217,3 @@ def sendTermCmd(self, command: Any) -> Any: except grpc.RpcError as exc: logging.error("SendTermCmd RPC failed: %s", exc) raise - diff --git a/C2Client/C2Client/libGrpcMessages/__init__.py b/C2Client/C2Client/libGrpcMessages/__init__.py new file mode 100644 index 0000000..b979bfc --- /dev/null +++ b/C2Client/C2Client/libGrpcMessages/__init__.py @@ -0,0 +1 @@ +# Vendored Python stubs for the TeamServer gRPC API. diff --git a/CMakeLists.txt b/CMakeLists.txt index 8156079..dcec3c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,8 +13,6 @@ set(C2_TEAMSERVER_RUNTIME_OUTPUT_DIR "${C2_RUNTIME_ROOT}/TeamServer") set(C2_RUNTIME_MODULE_OUTPUT_DIR "${C2_RUNTIME_ROOT}/TeamServerModules") set(C2_TEST_BIN_OUTPUT_DIR "${CMAKE_BINARY_DIR}/tests/bin") set(C2_GENERATED_PYTHON_GRPC_DIR "${CMAKE_BINARY_DIR}/generated/python_grpc") -set(C2_BASE64_SOURCE_DIR "${CMAKE_SOURCE_DIR}/thirdParty/base64") -set(C2_DONUT_SOURCE_DIR "${CMAKE_SOURCE_DIR}/thirdParty/donut") file(MAKE_DIRECTORY "${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}" @@ -88,9 +86,10 @@ endif() include_directories(thirdParty) -add_subdirectory(libs) - -add_subdirectory(thirdParty) +add_subdirectory(libs) +add_subdirectory(protocol) + +add_subdirectory(thirdParty) include_directories(thirdParty/base64) include_directories(thirdParty/donut/include) @@ -109,7 +108,7 @@ include_directories(core/beacon) include_directories(core/modules/ModuleCmd) add_subdirectory(teamServer) -add_subdirectory(core/modules) +add_subdirectory(core/modules) add_subdirectory(certs) diff --git a/core b/core index 18cf32f..1598c69 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 18cf32fefdacc0a8079817a24c753e4c42fcccc1 +Subproject commit 1598c69281a9435ba8d8a5c89acc91a75cb9800b diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index cab0cb8..b21f5e0 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -1,40 +1,3 @@ -# Collect .proto files -file(COPY ${CMAKE_SOURCE_DIR}/libs/libGrpcMessages/ DESTINATION ${CMAKE_BINARY_DIR}/libs/libGrpcMessages/) - -file(GLOB PROTO_FILES "${CMAKE_SOURCE_DIR}/libs/libGrpcMessages/src/*.proto") - -# Generate C++ gRPC files -execute_process( - COMMAND ${Protobuf_PROTOC_EXECUTABLE} - -I=${CMAKE_SOURCE_DIR}/libs/libGrpcMessages/src/ - --grpc_out=${CMAKE_BINARY_DIR}/libs/libGrpcMessages/build/cpp/src - --cpp_out=${CMAKE_BINARY_DIR}/libs/libGrpcMessages/build/cpp/src - --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN_PROGRAM} - ${PROTO_FILES} - RESULT_VARIABLE ret1 -) - -if(NOT ret1 EQUAL 0) - message(FATAL_ERROR "C++ gRPC generation failed with status: ${ret1}") -endif() - -# Generate Python gRPC files -execute_process( - COMMAND ${Protobuf_PROTOC_EXECUTABLE} - -I=${CMAKE_SOURCE_DIR}/libs/libGrpcMessages/src/ - --grpc_out=${C2_GENERATED_PYTHON_GRPC_DIR} - --python_out=${C2_GENERATED_PYTHON_GRPC_DIR} - --plugin=protoc-gen-grpc=${GRPC_PYTHON_PLUGIN_PROGRAM} - ${PROTO_FILES} - RESULT_VARIABLE ret2 -) - -if(NOT ret2 EQUAL 0) - message(FATAL_ERROR "Python gRPC generation failed with status: ${ret2}") -endif() - -add_subdirectory(libGrpcMessages) - add_subdirectory(libSocketHandler) add_subdirectory(libDns) diff --git a/libs/libGrpcMessages/CMakeLists.txt b/libs/libGrpcMessages/CMakeLists.txt deleted file mode 100644 index d3dfd16..0000000 --- a/libs/libGrpcMessages/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -cmake_minimum_required(VERSION 3.24) - -add_subdirectory(build/cpp/) \ No newline at end of file diff --git a/libs/libGrpcMessages/build/cpp/CMakeLists.txt b/libs/libGrpcMessages/build/cpp/CMakeLists.txt deleted file mode 100644 index 9a5b124..0000000 --- a/libs/libGrpcMessages/build/cpp/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -cmake_minimum_required(VERSION 3.24) -project(GrpcMessages) - -set(DEFAULT_BUILD_TYPE "Release") -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -file(GLOB_RECURSE SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/src/*.pb.cc) - -include_directories( -./src/ -${protobuf_INCLUDE_DIRS} -${gRPC_INCLUDE_DIRS} -${absl_INCLUDE_DIRS} -) - -add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) -target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/src) diff --git a/libs/libGrpcMessages/build/cpp/src/.gitignore b/libs/libGrpcMessages/build/cpp/src/.gitignore deleted file mode 100644 index b0fd46b..0000000 --- a/libs/libGrpcMessages/build/cpp/src/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!MakefileLinux diff --git a/protocol/CMakeLists.txt b/protocol/CMakeLists.txt new file mode 100644 index 0000000..25e1788 --- /dev/null +++ b/protocol/CMakeLists.txt @@ -0,0 +1,49 @@ +set(C2_PROTOCOL_PROTO_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +set(C2_PROTOCOL_PROTO_FILE "${C2_PROTOCOL_PROTO_DIR}/TeamServerApi.proto") +set(C2_PROTOCOL_CPP_OUT_DIR "${CMAKE_BINARY_DIR}/generated/grpc/cpp") + +set(C2_PROTOCOL_CPP_SRCS + "${C2_PROTOCOL_CPP_OUT_DIR}/TeamServerApi.pb.cc" + "${C2_PROTOCOL_CPP_OUT_DIR}/TeamServerApi.grpc.pb.cc" +) +set(C2_PROTOCOL_CPP_HDRS + "${C2_PROTOCOL_CPP_OUT_DIR}/TeamServerApi.pb.h" + "${C2_PROTOCOL_CPP_OUT_DIR}/TeamServerApi.grpc.pb.h" +) +set(C2_PROTOCOL_PY_SRCS + "${C2_GENERATED_PYTHON_GRPC_DIR}/TeamServerApi_pb2.py" + "${C2_GENERATED_PYTHON_GRPC_DIR}/TeamServerApi_pb2_grpc.py" +) + +add_custom_command( + OUTPUT ${C2_PROTOCOL_CPP_SRCS} ${C2_PROTOCOL_CPP_HDRS} + COMMAND ${CMAKE_COMMAND} -E make_directory "${C2_PROTOCOL_CPP_OUT_DIR}" + COMMAND ${Protobuf_PROTOC_EXECUTABLE} + --proto_path=${C2_PROTOCOL_PROTO_DIR} + --cpp_out=${C2_PROTOCOL_CPP_OUT_DIR} + --grpc_out=${C2_PROTOCOL_CPP_OUT_DIR} + --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN_PROGRAM} + ${C2_PROTOCOL_PROTO_FILE} + DEPENDS ${C2_PROTOCOL_PROTO_FILE} + VERBATIM +) + +add_custom_command( + OUTPUT ${C2_PROTOCOL_PY_SRCS} + COMMAND ${CMAKE_COMMAND} -E make_directory "${C2_GENERATED_PYTHON_GRPC_DIR}" + COMMAND ${Protobuf_PROTOC_EXECUTABLE} + --proto_path=${C2_PROTOCOL_PROTO_DIR} + --python_out=${C2_GENERATED_PYTHON_GRPC_DIR} + --grpc_out=${C2_GENERATED_PYTHON_GRPC_DIR} + --plugin=protoc-gen-grpc=${GRPC_PYTHON_PLUGIN_PROGRAM} + ${C2_PROTOCOL_PROTO_FILE} + DEPENDS ${C2_PROTOCOL_PROTO_FILE} + VERBATIM +) + +add_custom_target(protocol_python_stubs ALL DEPENDS ${C2_PROTOCOL_PY_SRCS}) + +add_library(GrpcMessages STATIC ${C2_PROTOCOL_CPP_SRCS}) +add_dependencies(GrpcMessages protocol_python_stubs) +target_include_directories(GrpcMessages PUBLIC "${C2_PROTOCOL_CPP_OUT_DIR}") +target_link_libraries(GrpcMessages PUBLIC protobuf::libprotobuf grpc::grpc) diff --git a/libs/libGrpcMessages/src/TeamServerApi.proto b/protocol/TeamServerApi.proto similarity index 87% rename from libs/libGrpcMessages/src/TeamServerApi.proto rename to protocol/TeamServerApi.proto index 8d92621..6b33791 100644 --- a/libs/libGrpcMessages/src/TeamServerApi.proto +++ b/protocol/TeamServerApi.proto @@ -49,19 +49,19 @@ enum Status } -message Response +message Response { Status status = 1; bytes message = 2; } -message Listener +message Listener { string listenerHash = 1; string type = 2; - int32 port = 3; - string ip = 4; + int32 port = 3; + string ip = 4; string project = 6; string token = 7; string domain = 8; @@ -70,13 +70,13 @@ message Listener } -message Session +message Session { string beaconHash = 1; - string listenerHash = 2; + string listenerHash = 2; string hostname = 3; - string username = 4; - string arch = 5; + string username = 4; + string arch = 5; string privilege = 6; string os = 7; string lastProofOfLife = 8; @@ -87,7 +87,7 @@ message Session } -message Command +message Command { string beaconHash = 1; string listenerHash = 2; @@ -95,7 +95,7 @@ message Command } -message CommandResponse +message CommandResponse { string beaconHash = 1; string instruction = 2; @@ -104,7 +104,7 @@ message CommandResponse } -message TermCommand +message TermCommand { string cmd = 1; string result = 2; From f5def78282f9e90c0d4c05f9ecc21dcf5541978a Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 13:49:55 +0200 Subject: [PATCH 07/28] Load client protocol bindings from build outputs --- .github/workflows/Tests.yml | 25 +- C2Client/C2Client/grpcClient.py | 7 +- C2Client/C2Client/libGrpcMessages/__init__.py | 1 - .../build/py/TeamServerApi_pb2.py | 56 --- .../build/py/TeamServerApi_pb2_grpc.py | 467 ------------------ C2Client/C2Client/protocol_bindings.py | 56 +++ C2Client/pyproject.toml | 3 +- C2Client/tests/test_gui_startup.py | 2 +- protocol/CMakeLists.txt | 20 +- protocol/PreparePythonGrpcPackage.cmake | 16 + 10 files changed, 118 insertions(+), 535 deletions(-) delete mode 100644 C2Client/C2Client/libGrpcMessages/__init__.py delete mode 100644 C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py delete mode 100644 C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py create mode 100644 C2Client/C2Client/protocol_bindings.py create mode 100644 protocol/PreparePythonGrpcPackage.cmake diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml index 942d731..ea4e8b7 100644 --- a/.github/workflows/Tests.yml +++ b/.github/workflows/Tests.yml @@ -1,6 +1,13 @@ name: Tests -on: workflow_dispatch +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + - master + - codex/** env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) @@ -48,6 +55,22 @@ jobs: - name: Run unit tests run: ctest --test-dir build + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install client test dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e "${{github.workspace}}/C2Client[test]" + + - name: Run client tests + env: + C2_PROTOCOL_PYTHON_ROOT: ${{github.workspace}}/build/generated/python_protocol + QT_QPA_PLATFORM: offscreen + run: python -m pytest "${{github.workspace}}/C2Client/tests" -q + - name: Configure CMake like the release run: cmake -B ${{github.workspace}}/buildRelease -DWITH_TESTS=OFF -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake diff --git a/C2Client/C2Client/grpcClient.py b/C2Client/C2Client/grpcClient.py index 2d9144b..fbc9682 100644 --- a/C2Client/C2Client/grpcClient.py +++ b/C2Client/C2Client/grpcClient.py @@ -7,16 +7,11 @@ import logging import os -import sys import uuid from typing import Any, Iterable, List, Tuple, Optional import grpc -from .libGrpcMessages.build.py import TeamServerApi_pb2 - -sys.modules.setdefault("TeamServerApi_pb2", TeamServerApi_pb2) - -from .libGrpcMessages.build.py import TeamServerApi_pb2_grpc +from .protocol_bindings import TeamServerApi_pb2, TeamServerApi_pb2_grpc MetadataType = List[Tuple[str, str]] diff --git a/C2Client/C2Client/libGrpcMessages/__init__.py b/C2Client/C2Client/libGrpcMessages/__init__.py deleted file mode 100644 index b979bfc..0000000 --- a/C2Client/C2Client/libGrpcMessages/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Vendored Python stubs for the TeamServer gRPC API. diff --git a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py b/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py deleted file mode 100644 index 457f8cb..0000000 --- a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: TeamServerApi.proto -# Protobuf Python Version: 5.27.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 5, - 27, - 0, - '', - 'TeamServerApi.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13TeamServerApi.proto\x12\rteamserverapi\"\x07\n\x05\x45mpty\"1\n\x0b\x41uthRequest\x12\x10\n\x08username\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\"U\n\x0c\x41uthResponse\x12%\n\x06status\x18\x01 \x01(\x0e\x32\x15.teamserverapi.Status\x12\r\n\x05token\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\"B\n\x08Response\x12%\n\x06status\x18\x01 \x01(\x0e\x32\x15.teamserverapi.Status\x12\x0f\n\x07message\x18\x02 \x01(\x0c\"\xa5\x01\n\x08Listener\x12\x14\n\x0clistenerHash\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0c\n\x04port\x18\x03 \x01(\x05\x12\n\n\x02ip\x18\x04 \x01(\t\x12\x0f\n\x07project\x18\x06 \x01(\t\x12\r\n\x05token\x18\x07 \x01(\t\x12\x0e\n\x06\x64omain\x18\x08 \x01(\t\x12\x17\n\x0fnumberOfSession\x18\x05 \x01(\x05\x12\x12\n\nbeaconHash\x18\t \x01(\t\"\xf4\x01\n\x07Session\x12\x12\n\nbeaconHash\x18\x01 \x01(\t\x12\x14\n\x0clistenerHash\x18\x02 \x01(\t\x12\x10\n\x08hostname\x18\x03 \x01(\t\x12\x10\n\x08username\x18\x04 \x01(\t\x12\x0c\n\x04\x61rch\x18\x05 \x01(\t\x12\x11\n\tprivilege\x18\x06 \x01(\t\x12\n\n\x02os\x18\x07 \x01(\t\x12\x17\n\x0flastProofOfLife\x18\x08 \x01(\t\x12\x0e\n\x06killed\x18\t \x01(\x08\x12\x13\n\x0binternalIps\x18\n \x01(\t\x12\x11\n\tprocessId\x18\x0b \x01(\t\x12\x1d\n\x15\x61\x64\x64itionalInformation\x18\x0c \x01(\t\"@\n\x07\x43ommand\x12\x12\n\nbeaconHash\x18\x01 \x01(\t\x12\x14\n\x0clistenerHash\x18\x02 \x01(\t\x12\x0b\n\x03\x63md\x18\x03 \x01(\t\"Y\n\x0f\x43ommandResponse\x12\x12\n\nbeaconHash\x18\x01 \x01(\t\x12\x13\n\x0binstruction\x18\x02 \x01(\t\x12\x0b\n\x03\x63md\x18\x03 \x01(\t\x12\x10\n\x08response\x18\x04 \x01(\x0c\"8\n\x0bTermCommand\x12\x0b\n\x03\x63md\x18\x01 \x01(\t\x12\x0e\n\x06result\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c*\x18\n\x06Status\x12\x06\n\x02OK\x10\x00\x12\x06\n\x02KO\x10\x01\x32\xd2\x05\n\rTeamServerApi\x12I\n\x0c\x41uthenticate\x12\x1a.teamserverapi.AuthRequest\x1a\x1b.teamserverapi.AuthResponse\"\x00\x12\x41\n\x0cGetListeners\x12\x14.teamserverapi.Empty\x1a\x17.teamserverapi.Listener\"\x00\x30\x01\x12\x41\n\x0b\x41\x64\x64Listener\x12\x17.teamserverapi.Listener\x1a\x17.teamserverapi.Response\"\x00\x12\x42\n\x0cStopListener\x12\x17.teamserverapi.Listener\x1a\x17.teamserverapi.Response\"\x00\x12?\n\x0bGetSessions\x12\x14.teamserverapi.Empty\x1a\x16.teamserverapi.Session\"\x00\x30\x01\x12@\n\x0bStopSession\x12\x16.teamserverapi.Session\x1a\x17.teamserverapi.Response\"\x00\x12\x43\n\x07GetHelp\x12\x16.teamserverapi.Command\x1a\x1e.teamserverapi.CommandResponse\"\x00\x12\x45\n\x10SendCmdToSession\x12\x16.teamserverapi.Command\x1a\x17.teamserverapi.Response\"\x00\x12T\n\x16GetResponseFromSession\x12\x16.teamserverapi.Session\x1a\x1e.teamserverapi.CommandResponse\"\x00\x30\x01\x12G\n\x0bSendTermCmd\x12\x1a.teamserverapi.TermCommand\x1a\x1a.teamserverapi.TermCommand\"\x00\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'TeamServerApi_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_STATUS']._serialized_start=883 - _globals['_STATUS']._serialized_end=907 - _globals['_EMPTY']._serialized_start=38 - _globals['_EMPTY']._serialized_end=45 - _globals['_AUTHREQUEST']._serialized_start=47 - _globals['_AUTHREQUEST']._serialized_end=96 - _globals['_AUTHRESPONSE']._serialized_start=98 - _globals['_AUTHRESPONSE']._serialized_end=183 - _globals['_RESPONSE']._serialized_start=185 - _globals['_RESPONSE']._serialized_end=251 - _globals['_LISTENER']._serialized_start=254 - _globals['_LISTENER']._serialized_end=419 - _globals['_SESSION']._serialized_start=422 - _globals['_SESSION']._serialized_end=666 - _globals['_COMMAND']._serialized_start=668 - _globals['_COMMAND']._serialized_end=732 - _globals['_COMMANDRESPONSE']._serialized_start=734 - _globals['_COMMANDRESPONSE']._serialized_end=823 - _globals['_TERMCOMMAND']._serialized_start=825 - _globals['_TERMCOMMAND']._serialized_end=881 - _globals['_TEAMSERVERAPI']._serialized_start=910 - _globals['_TEAMSERVERAPI']._serialized_end=1632 -# @@protoc_insertion_point(module_scope) diff --git a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py b/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py deleted file mode 100644 index 6555949..0000000 --- a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py +++ /dev/null @@ -1,467 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -import TeamServerApi_pb2 as TeamServerApi__pb2 - - -class TeamServerApiStub(object): - """Interface exported by the server. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.Authenticate = channel.unary_unary( - '/teamserverapi.TeamServerApi/Authenticate', - request_serializer=TeamServerApi__pb2.AuthRequest.SerializeToString, - response_deserializer=TeamServerApi__pb2.AuthResponse.FromString, - _registered_method=True) - self.GetListeners = channel.unary_stream( - '/teamserverapi.TeamServerApi/GetListeners', - request_serializer=TeamServerApi__pb2.Empty.SerializeToString, - response_deserializer=TeamServerApi__pb2.Listener.FromString, - _registered_method=True) - self.AddListener = channel.unary_unary( - '/teamserverapi.TeamServerApi/AddListener', - request_serializer=TeamServerApi__pb2.Listener.SerializeToString, - response_deserializer=TeamServerApi__pb2.Response.FromString, - _registered_method=True) - self.StopListener = channel.unary_unary( - '/teamserverapi.TeamServerApi/StopListener', - request_serializer=TeamServerApi__pb2.Listener.SerializeToString, - response_deserializer=TeamServerApi__pb2.Response.FromString, - _registered_method=True) - self.GetSessions = channel.unary_stream( - '/teamserverapi.TeamServerApi/GetSessions', - request_serializer=TeamServerApi__pb2.Empty.SerializeToString, - response_deserializer=TeamServerApi__pb2.Session.FromString, - _registered_method=True) - self.StopSession = channel.unary_unary( - '/teamserverapi.TeamServerApi/StopSession', - request_serializer=TeamServerApi__pb2.Session.SerializeToString, - response_deserializer=TeamServerApi__pb2.Response.FromString, - _registered_method=True) - self.GetHelp = channel.unary_unary( - '/teamserverapi.TeamServerApi/GetHelp', - request_serializer=TeamServerApi__pb2.Command.SerializeToString, - response_deserializer=TeamServerApi__pb2.CommandResponse.FromString, - _registered_method=True) - self.SendCmdToSession = channel.unary_unary( - '/teamserverapi.TeamServerApi/SendCmdToSession', - request_serializer=TeamServerApi__pb2.Command.SerializeToString, - response_deserializer=TeamServerApi__pb2.Response.FromString, - _registered_method=True) - self.GetResponseFromSession = channel.unary_stream( - '/teamserverapi.TeamServerApi/GetResponseFromSession', - request_serializer=TeamServerApi__pb2.Session.SerializeToString, - response_deserializer=TeamServerApi__pb2.CommandResponse.FromString, - _registered_method=True) - self.SendTermCmd = channel.unary_unary( - '/teamserverapi.TeamServerApi/SendTermCmd', - request_serializer=TeamServerApi__pb2.TermCommand.SerializeToString, - response_deserializer=TeamServerApi__pb2.TermCommand.FromString, - _registered_method=True) - - -class TeamServerApiServicer(object): - """Interface exported by the server. - """ - - def Authenticate(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetListeners(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def AddListener(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def StopListener(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetSessions(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def StopSession(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetHelp(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SendCmdToSession(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetResponseFromSession(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SendTermCmd(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_TeamServerApiServicer_to_server(servicer, server): - rpc_method_handlers = { - 'Authenticate': grpc.unary_unary_rpc_method_handler( - servicer.Authenticate, - request_deserializer=TeamServerApi__pb2.AuthRequest.FromString, - response_serializer=TeamServerApi__pb2.AuthResponse.SerializeToString, - ), - 'GetListeners': grpc.unary_stream_rpc_method_handler( - servicer.GetListeners, - request_deserializer=TeamServerApi__pb2.Empty.FromString, - response_serializer=TeamServerApi__pb2.Listener.SerializeToString, - ), - 'AddListener': grpc.unary_unary_rpc_method_handler( - servicer.AddListener, - request_deserializer=TeamServerApi__pb2.Listener.FromString, - response_serializer=TeamServerApi__pb2.Response.SerializeToString, - ), - 'StopListener': grpc.unary_unary_rpc_method_handler( - servicer.StopListener, - request_deserializer=TeamServerApi__pb2.Listener.FromString, - response_serializer=TeamServerApi__pb2.Response.SerializeToString, - ), - 'GetSessions': grpc.unary_stream_rpc_method_handler( - servicer.GetSessions, - request_deserializer=TeamServerApi__pb2.Empty.FromString, - response_serializer=TeamServerApi__pb2.Session.SerializeToString, - ), - 'StopSession': grpc.unary_unary_rpc_method_handler( - servicer.StopSession, - request_deserializer=TeamServerApi__pb2.Session.FromString, - response_serializer=TeamServerApi__pb2.Response.SerializeToString, - ), - 'GetHelp': grpc.unary_unary_rpc_method_handler( - servicer.GetHelp, - request_deserializer=TeamServerApi__pb2.Command.FromString, - response_serializer=TeamServerApi__pb2.CommandResponse.SerializeToString, - ), - 'SendCmdToSession': grpc.unary_unary_rpc_method_handler( - servicer.SendCmdToSession, - request_deserializer=TeamServerApi__pb2.Command.FromString, - response_serializer=TeamServerApi__pb2.Response.SerializeToString, - ), - 'GetResponseFromSession': grpc.unary_stream_rpc_method_handler( - servicer.GetResponseFromSession, - request_deserializer=TeamServerApi__pb2.Session.FromString, - response_serializer=TeamServerApi__pb2.CommandResponse.SerializeToString, - ), - 'SendTermCmd': grpc.unary_unary_rpc_method_handler( - servicer.SendTermCmd, - request_deserializer=TeamServerApi__pb2.TermCommand.FromString, - response_serializer=TeamServerApi__pb2.TermCommand.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'teamserverapi.TeamServerApi', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('teamserverapi.TeamServerApi', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class TeamServerApi(object): - """Interface exported by the server. - """ - - @staticmethod - def Authenticate(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/teamserverapi.TeamServerApi/Authenticate', - TeamServerApi__pb2.AuthRequest.SerializeToString, - TeamServerApi__pb2.AuthResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetListeners(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream( - request, - target, - '/teamserverapi.TeamServerApi/GetListeners', - TeamServerApi__pb2.Empty.SerializeToString, - TeamServerApi__pb2.Listener.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def AddListener(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/teamserverapi.TeamServerApi/AddListener', - TeamServerApi__pb2.Listener.SerializeToString, - TeamServerApi__pb2.Response.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def StopListener(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/teamserverapi.TeamServerApi/StopListener', - TeamServerApi__pb2.Listener.SerializeToString, - TeamServerApi__pb2.Response.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetSessions(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream( - request, - target, - '/teamserverapi.TeamServerApi/GetSessions', - TeamServerApi__pb2.Empty.SerializeToString, - TeamServerApi__pb2.Session.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def StopSession(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/teamserverapi.TeamServerApi/StopSession', - TeamServerApi__pb2.Session.SerializeToString, - TeamServerApi__pb2.Response.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetHelp(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/teamserverapi.TeamServerApi/GetHelp', - TeamServerApi__pb2.Command.SerializeToString, - TeamServerApi__pb2.CommandResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def SendCmdToSession(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/teamserverapi.TeamServerApi/SendCmdToSession', - TeamServerApi__pb2.Command.SerializeToString, - TeamServerApi__pb2.Response.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetResponseFromSession(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream( - request, - target, - '/teamserverapi.TeamServerApi/GetResponseFromSession', - TeamServerApi__pb2.Session.SerializeToString, - TeamServerApi__pb2.CommandResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def SendTermCmd(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/teamserverapi.TeamServerApi/SendTermCmd', - TeamServerApi__pb2.TermCommand.SerializeToString, - TeamServerApi__pb2.TermCommand.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/C2Client/C2Client/protocol_bindings.py b/C2Client/C2Client/protocol_bindings.py new file mode 100644 index 0000000..6d8b005 --- /dev/null +++ b/C2Client/C2Client/protocol_bindings.py @@ -0,0 +1,56 @@ +"""Helpers to load generated TeamServer gRPC bindings.""" + +from __future__ import annotations + +import importlib +import os +import sys +from pathlib import Path +from typing import Tuple + + +def _candidate_protocol_roots() -> list[Path]: + candidates: list[Path] = [] + + env_value = os.getenv("C2_PROTOCOL_PYTHON_ROOT") + if env_value: + candidates.append(Path(env_value).expanduser()) + + repo_root = Path(__file__).resolve().parents[2] + candidates.extend(sorted(repo_root.glob("build*/generated/python_protocol"))) + candidates.append(repo_root / "build" / "generated" / "python_protocol") + + unique_candidates: list[Path] = [] + seen: set[Path] = set() + for candidate in candidates: + if candidate in seen: + continue + seen.add(candidate) + unique_candidates.append(candidate) + return unique_candidates + + +def _ensure_protocol_package_on_path() -> None: + for candidate in _candidate_protocol_roots(): + package_file = candidate / "c2client_protocol" / "TeamServerApi_pb2.py" + if not package_file.exists(): + continue + candidate_str = str(candidate) + if candidate_str not in sys.path: + sys.path.insert(0, candidate_str) + return + + raise ModuleNotFoundError( + "Unable to locate generated TeamServer protocol bindings. " + "Run the CMake build first or set C2_PROTOCOL_PYTHON_ROOT.", + ) + + +def load_protocol_modules() -> Tuple[object, object]: + _ensure_protocol_package_on_path() + teamserverapi_pb2 = importlib.import_module("c2client_protocol.TeamServerApi_pb2") + teamserverapi_pb2_grpc = importlib.import_module("c2client_protocol.TeamServerApi_pb2_grpc") + return teamserverapi_pb2, teamserverapi_pb2_grpc + + +TeamServerApi_pb2, TeamServerApi_pb2_grpc = load_protocol_modules() diff --git a/C2Client/pyproject.toml b/C2Client/pyproject.toml index 21b2254..959aa46 100644 --- a/C2Client/pyproject.toml +++ b/C2Client/pyproject.toml @@ -29,7 +29,7 @@ test = [ [tool.setuptools.packages.find] where = ["."] -include = ["C2Client*", "C2Client.libGrpcMessages*", "C2Client.TerminalModules.*"] +include = ["C2Client*", "C2Client.TerminalModules.*"] [tool.setuptools.package-data] C2Client = [ @@ -37,7 +37,6 @@ C2Client = [ "logs/*", "Scripts/*.py", "server.crt", - "libGrpcMessages/build/py/*.py", "DropperModules.conf", "ShellCodeModules.conf" ] diff --git a/C2Client/tests/test_gui_startup.py b/C2Client/tests/test_gui_startup.py index f4a5726..fd883ec 100644 --- a/C2Client/tests/test_gui_startup.py +++ b/C2Client/tests/test_gui_startup.py @@ -38,7 +38,7 @@ def addConsole(self, *args, **kwargs): def test_gui_startup(qtbot, monkeypatch): - monkeypatch.setattr(GUI, 'GrpcClient', lambda ip, port, dev: object()) + monkeypatch.setattr(GUI, 'GrpcClient', lambda *args, **kwargs: object()) def fake_top(self): self.sessionsWidget = DummyWidget() diff --git a/protocol/CMakeLists.txt b/protocol/CMakeLists.txt index 25e1788..06df85b 100644 --- a/protocol/CMakeLists.txt +++ b/protocol/CMakeLists.txt @@ -1,6 +1,8 @@ set(C2_PROTOCOL_PROTO_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(C2_PROTOCOL_PROTO_FILE "${C2_PROTOCOL_PROTO_DIR}/TeamServerApi.proto") set(C2_PROTOCOL_CPP_OUT_DIR "${CMAKE_BINARY_DIR}/generated/grpc/cpp") +set(C2_PROTOCOL_PYTHON_PACKAGE_ROOT "${CMAKE_BINARY_DIR}/generated/python_protocol") +set(C2_PROTOCOL_PYTHON_PACKAGE_DIR "${C2_PROTOCOL_PYTHON_PACKAGE_ROOT}/c2client_protocol") set(C2_PROTOCOL_CPP_SRCS "${C2_PROTOCOL_CPP_OUT_DIR}/TeamServerApi.pb.cc" @@ -14,6 +16,11 @@ set(C2_PROTOCOL_PY_SRCS "${C2_GENERATED_PYTHON_GRPC_DIR}/TeamServerApi_pb2.py" "${C2_GENERATED_PYTHON_GRPC_DIR}/TeamServerApi_pb2_grpc.py" ) +set(C2_PROTOCOL_PY_PACKAGE_FILES + "${C2_PROTOCOL_PYTHON_PACKAGE_DIR}/__init__.py" + "${C2_PROTOCOL_PYTHON_PACKAGE_DIR}/TeamServerApi_pb2.py" + "${C2_PROTOCOL_PYTHON_PACKAGE_DIR}/TeamServerApi_pb2_grpc.py" +) add_custom_command( OUTPUT ${C2_PROTOCOL_CPP_SRCS} ${C2_PROTOCOL_CPP_HDRS} @@ -41,7 +48,18 @@ add_custom_command( VERBATIM ) -add_custom_target(protocol_python_stubs ALL DEPENDS ${C2_PROTOCOL_PY_SRCS}) +add_custom_command( + OUTPUT ${C2_PROTOCOL_PY_PACKAGE_FILES} + COMMAND ${CMAKE_COMMAND} + -DINPUT_PY_PB2=${C2_GENERATED_PYTHON_GRPC_DIR}/TeamServerApi_pb2.py + -DINPUT_PY_GRPC=${C2_GENERATED_PYTHON_GRPC_DIR}/TeamServerApi_pb2_grpc.py + -DOUTPUT_DIR=${C2_PROTOCOL_PYTHON_PACKAGE_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/PreparePythonGrpcPackage.cmake + DEPENDS ${C2_PROTOCOL_PY_SRCS} ${CMAKE_CURRENT_SOURCE_DIR}/PreparePythonGrpcPackage.cmake + VERBATIM +) + +add_custom_target(protocol_python_stubs ALL DEPENDS ${C2_PROTOCOL_PY_PACKAGE_FILES}) add_library(GrpcMessages STATIC ${C2_PROTOCOL_CPP_SRCS}) add_dependencies(GrpcMessages protocol_python_stubs) diff --git a/protocol/PreparePythonGrpcPackage.cmake b/protocol/PreparePythonGrpcPackage.cmake new file mode 100644 index 0000000..8ff88d8 --- /dev/null +++ b/protocol/PreparePythonGrpcPackage.cmake @@ -0,0 +1,16 @@ +if(NOT DEFINED INPUT_PY_PB2 OR NOT DEFINED INPUT_PY_GRPC OR NOT DEFINED OUTPUT_DIR) + message(FATAL_ERROR "PreparePythonGrpcPackage.cmake requires INPUT_PY_PB2, INPUT_PY_GRPC and OUTPUT_DIR.") +endif() + +file(MAKE_DIRECTORY "${OUTPUT_DIR}") +file(COPY "${INPUT_PY_PB2}" DESTINATION "${OUTPUT_DIR}") + +file(READ "${INPUT_PY_GRPC}" grpc_stub_content) +string(REPLACE + "import TeamServerApi_pb2 as TeamServerApi__pb2" + "from . import TeamServerApi_pb2 as TeamServerApi__pb2" + grpc_stub_content + "${grpc_stub_content}" +) +file(WRITE "${OUTPUT_DIR}/TeamServerApi_pb2_grpc.py" "${grpc_stub_content}") +file(WRITE "${OUTPUT_DIR}/__init__.py" "") From 93e4375c76463c64ff822f3204e6f4c09e7c947d Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 14:20:49 +0200 Subject: [PATCH 08/28] Stage release bundles from build outputs --- .github/workflows/Release.yml | 99 ++++++++++------ C2Client/C2Client/protocol_bindings.py | 8 ++ C2Client/tests/test_protocol_bindings.py | 32 +++++ CMakeLists.txt | 7 +- packaging/CMakeLists.txt | 15 +++ packaging/assemble_release.py | 143 +++++++++++++++++++++++ 6 files changed, 267 insertions(+), 37 deletions(-) create mode 100644 C2Client/tests/test_protocol_bindings.py create mode 100644 packaging/CMakeLists.txt create mode 100644 packaging/assemble_release.py diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 12da470..5503f5b 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -1,6 +1,6 @@ name: Release -on: +on: workflow_dispatch: push: tags: @@ -16,32 +16,27 @@ permissions: jobs: buildAndRelease: - # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. - # You can convert this to a matrix build if you need cross-platform coverage. - # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 + with: + submodules: recursive - - name: Install Samba client dev headers + - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y libsmbclient-dev - - # Update references - - name: Git Sumbodule Update - run: | - git submodule update --init + sudo apt-get install -y jq libsmbclient-dev unzip - # Needed to generate certificates - uses: ConorMacBride/install-package@v1 with: apt: golang-cfssl - + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Get Conan - # You may pin to the exact commit or the version. - # uses: turtlebrowser/get-conan@c171f295f3f507360ee018736a6608731aa2109d uses: turtlebrowser/get-conan@v1.2 - name: Create default profile @@ -53,31 +48,67 @@ jobs: - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 18 - - name: Prep release + - name: Stage release bundle + run: | + cmake --build ${{github.workspace}}/build --target stage_release_bundle + + - name: Import implant releases run: | - rm -rf Release/Beacons - rm -f Release/Client/.gitignore - rm -f Release/Client/logs/.gitignore - rm -f Release/Client/libGrpcMessages/build/py/.gitignore - rm -f Release/Modules/.gitignore - mv ./Release/Modules/ ./Release/TeamServerModules/ - rm -f Release/Scripts/.gitignore - rm -f Release/TeamServer/.gitignore - rm -f Release/Tools/.gitignore - rm -f Release/www/.gitignore - wget -q $(wget -q -O - 'https://api.github.com/repos/maxDcb/C2Implant/releases/latest' | jq -r '.assets[] | select(.name=="Release.zip").browser_download_url') -O ./C2Implant.zip - unzip -o C2Implant.zip - rm -f C2Implant.zip - wget -q $(wget -q -O - 'https://api.github.com/repos/maxDcb/C2LinuxImplant/releases/latest' | jq -r '.assets[] | select(.name=="Release.tar.gz").browser_download_url') -O ./C2LinuxImplant.tar.gz - tar -zxvf C2LinuxImplant.tar.gz - rm -f C2LinuxImplant.tar.gz - tar -zcvf Release.tar.gz Release + set -euo pipefail + + stage_root="${{github.workspace}}/build/release-staging/Release" + import_root="${{github.workspace}}/build/release-imports" + rm -rf "${import_root}" + mkdir -p "${import_root}/windows" "${import_root}/linux" + + windows_url="$(wget -q -O - 'https://api.github.com/repos/maxDcb/C2Implant/releases/latest' | jq -r '.assets[] | select(.name=="Release.zip").browser_download_url')" + linux_url="$(wget -q -O - 'https://api.github.com/repos/maxDcb/C2LinuxImplant/releases/latest' | jq -r '.assets[] | select(.name=="Release.tar.gz").browser_download_url')" + + wget -q "${windows_url}" -O "${import_root}/C2Implant.zip" + unzip -oq "${import_root}/C2Implant.zip" -d "${import_root}/windows" + + wget -q "${linux_url}" -O "${import_root}/C2LinuxImplant.tar.gz" + tar -xzf "${import_root}/C2LinuxImplant.tar.gz" -C "${import_root}/linux" + + if [ -d "${import_root}/windows/Release/Beacons" ]; then + rm -rf "${stage_root}/WindowsBeacons" + cp -R "${import_root}/windows/Release/Beacons" "${stage_root}/WindowsBeacons" + elif [ -d "${import_root}/windows/Release/WindowsBeacons" ]; then + rm -rf "${stage_root}/WindowsBeacons" + cp -R "${import_root}/windows/Release/WindowsBeacons" "${stage_root}/WindowsBeacons" + fi + + if [ -d "${import_root}/windows/Release/Modules" ]; then + rm -rf "${stage_root}/WindowsModules" + cp -R "${import_root}/windows/Release/Modules" "${stage_root}/WindowsModules" + elif [ -d "${import_root}/windows/Release/WindowsModules" ]; then + rm -rf "${stage_root}/WindowsModules" + cp -R "${import_root}/windows/Release/WindowsModules" "${stage_root}/WindowsModules" + fi + + if [ -d "${import_root}/linux/Release/Beacons" ]; then + rm -rf "${stage_root}/LinuxBeacons" + cp -R "${import_root}/linux/Release/Beacons" "${stage_root}/LinuxBeacons" + elif [ -d "${import_root}/linux/Release/LinuxBeacons" ]; then + rm -rf "${stage_root}/LinuxBeacons" + cp -R "${import_root}/linux/Release/LinuxBeacons" "${stage_root}/LinuxBeacons" + fi + + if [ -d "${import_root}/linux/Release/Modules" ]; then + rm -rf "${stage_root}/LinuxModules" + cp -R "${import_root}/linux/Release/Modules" "${stage_root}/LinuxModules" + elif [ -d "${import_root}/linux/Release/LinuxModules" ]; then + rm -rf "${stage_root}/LinuxModules" + cp -R "${import_root}/linux/Release/LinuxModules" "${stage_root}/LinuxModules" + fi + + tar -zcf "${{github.workspace}}/Release.tar.gz" -C "${{github.workspace}}/build/release-staging" Release - name: Upload release uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: Release.tar.gz + file: ${{github.workspace}}/Release.tar.gz asset_name: Release.tar.gz tag: ${{ github.ref }} overwrite: true diff --git a/C2Client/C2Client/protocol_bindings.py b/C2Client/C2Client/protocol_bindings.py index 6d8b005..8b94268 100644 --- a/C2Client/C2Client/protocol_bindings.py +++ b/C2Client/C2Client/protocol_bindings.py @@ -47,6 +47,14 @@ def _ensure_protocol_package_on_path() -> None: def load_protocol_modules() -> Tuple[object, object]: + try: + teamserverapi_pb2 = importlib.import_module("c2client_protocol.TeamServerApi_pb2") + teamserverapi_pb2_grpc = importlib.import_module("c2client_protocol.TeamServerApi_pb2_grpc") + return teamserverapi_pb2, teamserverapi_pb2_grpc + except ModuleNotFoundError as exc: + if not exc.name or not exc.name.startswith("c2client_protocol"): + raise + _ensure_protocol_package_on_path() teamserverapi_pb2 = importlib.import_module("c2client_protocol.TeamServerApi_pb2") teamserverapi_pb2_grpc = importlib.import_module("c2client_protocol.TeamServerApi_pb2_grpc") diff --git a/C2Client/tests/test_protocol_bindings.py b/C2Client/tests/test_protocol_bindings.py new file mode 100644 index 0000000..f2dd92f --- /dev/null +++ b/C2Client/tests/test_protocol_bindings.py @@ -0,0 +1,32 @@ +import importlib +import sys +from pathlib import Path + + +def test_protocol_bindings_loads_importable_package_without_build_tree(monkeypatch, tmp_path): + package_dir = tmp_path / "c2client_protocol" + package_dir.mkdir(parents=True) + + (package_dir / "__init__.py").write_text("", encoding="utf-8") + (package_dir / "TeamServerApi_pb2.py").write_text("VALUE = 1\n", encoding="utf-8") + (package_dir / "TeamServerApi_pb2_grpc.py").write_text( + "from . import TeamServerApi_pb2\n" + "VALUE = TeamServerApi_pb2.VALUE\n", + encoding="utf-8", + ) + + monkeypatch.syspath_prepend(str(tmp_path)) + monkeypatch.delenv("C2_PROTOCOL_PYTHON_ROOT", raising=False) + + for module_name in ( + "C2Client.protocol_bindings", + "c2client_protocol", + "c2client_protocol.TeamServerApi_pb2", + "c2client_protocol.TeamServerApi_pb2_grpc", + ): + sys.modules.pop(module_name, None) + + protocol_bindings = importlib.import_module("C2Client.protocol_bindings") + + assert protocol_bindings.TeamServerApi_pb2.VALUE == 1 + assert protocol_bindings.TeamServerApi_pb2_grpc.VALUE == 1 diff --git a/CMakeLists.txt b/CMakeLists.txt index dcec3c3..5981e71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,10 +107,11 @@ include_directories(core/listener) include_directories(core/beacon) include_directories(core/modules/ModuleCmd) -add_subdirectory(teamServer) +add_subdirectory(teamServer) add_subdirectory(core/modules) - -add_subdirectory(certs) + +add_subdirectory(certs) +add_subdirectory(packaging) diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt new file mode 100644 index 0000000..938ea29 --- /dev/null +++ b/packaging/CMakeLists.txt @@ -0,0 +1,15 @@ +find_package(Python3 COMPONENTS Interpreter REQUIRED) + +set(C2_RELEASE_STAGING_ROOT "${CMAKE_BINARY_DIR}/release-staging") +set(C2_RELEASE_STAGING_DIR "${C2_RELEASE_STAGING_ROOT}/Release") + +add_custom_target(stage_release_bundle + COMMAND ${Python3_EXECUTABLE} + "${CMAKE_CURRENT_SOURCE_DIR}/assemble_release.py" + --source-root "${CMAKE_SOURCE_DIR}" + --build-root "${CMAKE_BINARY_DIR}" + --output-root "${C2_RELEASE_STAGING_DIR}" + DEPENDS TeamServer protocol_python_stubs + COMMENT "Staging release bundle" + VERBATIM +) diff --git a/packaging/assemble_release.py b/packaging/assemble_release.py new file mode 100644 index 0000000..ec8135d --- /dev/null +++ b/packaging/assemble_release.py @@ -0,0 +1,143 @@ +from __future__ import annotations + +import argparse +import shutil +from pathlib import Path + + +def _copytree(src: Path, dst: Path) -> None: + shutil.copytree(src, dst, dirs_exist_ok=True) + + +def _remove_matching(root: Path, pattern: str) -> None: + for path in root.rglob(pattern): + if path.is_dir(): + shutil.rmtree(path) + elif path.exists(): + path.unlink() + + +def _write_text(path: Path, content: str) -> None: + path.write_text(content, encoding="utf-8") + + +def _build_client_bundle(source_root: Path, build_root: Path, release_root: Path) -> None: + client_bundle_root = release_root / "Client" + client_package_root = source_root / "C2Client" / "C2Client" + protocol_package_root = build_root / "generated" / "python_protocol" / "c2client_protocol" + + if not protocol_package_root.exists(): + raise FileNotFoundError( + f"Missing generated client protocol package: {protocol_package_root}. " + "Build the project before staging the release.", + ) + + shutil.rmtree(client_bundle_root, ignore_errors=True) + client_bundle_root.mkdir(parents=True, exist_ok=True) + + _copytree(client_package_root, client_bundle_root / "C2Client") + _copytree(protocol_package_root, client_bundle_root / "c2client_protocol") + + for metadata_name in ("pyproject.toml", "requirements.txt"): + shutil.copy2(source_root / "C2Client" / metadata_name, client_bundle_root / metadata_name) + + _write_text( + client_bundle_root / "run-client.sh", + """#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +export PYTHONPATH="${SCRIPT_DIR}${PYTHONPATH:+:${PYTHONPATH}}" + +exec python -m C2Client.GUI "$@" +""", + ) + (client_bundle_root / "run-client.sh").chmod(0o755) + + _write_text( + client_bundle_root / "run-client.ps1", + """$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +if ($env:PYTHONPATH) { + $env:PYTHONPATH = \"$scriptDir;$env:PYTHONPATH\" +} else { + $env:PYTHONPATH = $scriptDir +} + +python -m C2Client.GUI @args +""", + ) + + _write_text( + client_bundle_root / "README.md", + """# C2Client + +This bundle contains the Python client sources and the generated `c2client_protocol` package +produced from the repository's `protocol/TeamServerApi.proto`. + +Typical local setup: + +```bash +python -m venv .venv +. .venv/bin/activate +pip install -r requirements.txt +./run-client.sh --ip 127.0.0.1 --port 50051 +``` + +The launcher adds the bundle root to `PYTHONPATH`, so the generated protocol bindings are +resolved without relying on build-tree paths. +""", + ) + + +def assemble_release(source_root: Path, build_root: Path, output_root: Path) -> None: + tracked_release_root = source_root / "Release" + build_release_root = build_root / "artifacts" / "Release" + teamserver_root = build_release_root / "TeamServer" + modules_root = build_release_root / "TeamServerModules" + + if not tracked_release_root.exists(): + raise FileNotFoundError(f"Missing tracked release assets directory: {tracked_release_root}") + if not teamserver_root.exists(): + raise FileNotFoundError( + f"Missing TeamServer runtime artifacts: {teamserver_root}. " + "Build the project before staging the release.", + ) + if not modules_root.exists(): + raise FileNotFoundError( + f"Missing TeamServer module artifacts: {modules_root}. " + "Build the project before staging the release.", + ) + + shutil.rmtree(output_root, ignore_errors=True) + output_root.parent.mkdir(parents=True, exist_ok=True) + _copytree(tracked_release_root, output_root) + + shutil.rmtree(output_root / "TeamServer", ignore_errors=True) + shutil.rmtree(output_root / "TeamServerModules", ignore_errors=True) + shutil.rmtree(output_root / "Modules", ignore_errors=True) + + _copytree(teamserver_root, output_root / "TeamServer") + _copytree(modules_root, output_root / "TeamServerModules") + + _build_client_bundle(source_root, build_root, output_root) + + _remove_matching(output_root, ".gitignore") + _remove_matching(output_root, "__pycache__") + + +def main() -> None: + parser = argparse.ArgumentParser(description="Stage a release bundle from build outputs.") + parser.add_argument("--source-root", required=True) + parser.add_argument("--build-root", required=True) + parser.add_argument("--output-root", required=True) + args = parser.parse_args() + + assemble_release( + source_root=Path(args.source_root).resolve(), + build_root=Path(args.build_root).resolve(), + output_root=Path(args.output_root).resolve(), + ) + + +if __name__ == "__main__": + main() From 29d02425a648f0639fa19232cdf03464eaf6b812 Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 14:55:57 +0200 Subject: [PATCH 09/28] Split TeamServer auth and bootstrap --- teamServer/CMakeLists.txt | 21 +- teamServer/teamServer/TeamServer.cpp | 519 +----------------- teamServer/teamServer/TeamServer.hpp | 12 +- teamServer/teamServer/TeamServerAuth.cpp | 287 ++++++++++ teamServer/teamServer/TeamServerAuth.hpp | 39 ++ teamServer/teamServer/TeamServerBootstrap.cpp | 149 +++++ teamServer/teamServer/TeamServerBootstrap.hpp | 32 ++ .../teamServer/TeamServerRuntimeConfig.cpp | 70 +++ .../teamServer/TeamServerRuntimeConfig.hpp | 30 + teamServer/teamServer/main.cpp | 32 ++ teamServer/tests/testsTestServer.cpp | 171 +++++- 11 files changed, 853 insertions(+), 509 deletions(-) create mode 100644 teamServer/teamServer/TeamServerAuth.cpp create mode 100644 teamServer/teamServer/TeamServerAuth.hpp create mode 100644 teamServer/teamServer/TeamServerBootstrap.cpp create mode 100644 teamServer/teamServer/TeamServerBootstrap.hpp create mode 100644 teamServer/teamServer/TeamServerRuntimeConfig.cpp create mode 100644 teamServer/teamServer/TeamServerRuntimeConfig.hpp create mode 100644 teamServer/teamServer/main.cpp diff --git a/teamServer/CMakeLists.txt b/teamServer/CMakeLists.txt index 757a827..43dee40 100644 --- a/teamServer/CMakeLists.txt +++ b/teamServer/CMakeLists.txt @@ -4,6 +4,10 @@ include_directories(../core/modules/ModuleCmd) set(SOURCES_TEAMSERVER teamServer/TeamServer.cpp +teamServer/TeamServerAuth.cpp +teamServer/TeamServerRuntimeConfig.cpp +teamServer/TeamServerBootstrap.cpp +teamServer/main.cpp ../core/listener/Listener.cpp ../core/listener/ListenerTcp.cpp ../core/listener/ListenerHttp.cpp @@ -28,8 +32,21 @@ add_custom_command(TARGET TeamServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/teamServer/teamServer/auth_credentials.json "${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/auth_credentials.json") if(WITH_TESTS) - add_executable(testsTestServer tests/testsTestServer.cpp ) - target_link_libraries(testsTestServer ) + add_executable(testsTestServer + tests/testsTestServer.cpp + teamServer/TeamServerAuth.cpp + teamServer/TeamServerRuntimeConfig.cpp + teamServer/TeamServerBootstrap.cpp + ) + target_include_directories(testsTestServer PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/teamServer + ${CMAKE_SOURCE_DIR}/libs/libSocks5/src + ) + if(WIN32) + target_link_libraries(testsTestServer GrpcMessages openssl::openssl ${OPENSSL_CRYPTO_LIBRARY} ZLIB::ZLIB grpc::grpc spdlog::spdlog) + else() + target_link_libraries(testsTestServer GrpcMessages pthread openssl::openssl ZLIB::ZLIB grpc::grpc spdlog::spdlog httplib::httplib Crow::Crow dl rt) + endif() add_custom_command(TARGET testsTestServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ "${C2_TEST_BIN_OUTPUT_DIR}/$") diff --git a/teamServer/teamServer/TeamServer.cpp b/teamServer/teamServer/TeamServer.cpp index 39d915b..3cbbb07 100644 --- a/teamServer/teamServer/TeamServer.cpp +++ b/teamServer/teamServer/TeamServer.cpp @@ -1,18 +1,19 @@ #include "TeamServer.hpp" +#include "TeamServerAuth.hpp" +#include "TeamServerBootstrap.hpp" +#include "TeamServerRuntimeConfig.hpp" + #include #include #include #include -#include #include #include -#include #include #include #include -#include using namespace std; using namespace std::placeholders; @@ -22,37 +23,6 @@ using json = nlohmann::json; typedef ModuleCmd* (*constructProc)(); -namespace -{ -spdlog::level::level_enum parseLogLevel(std::string level, bool& isUnknown) -{ - isUnknown = false; - - std::transform(level.begin(), level.end(), level.begin(), - [](unsigned char c) - { return static_cast(std::tolower(c)); }); - - static const std::unordered_map levelMap = - { - {"trace", spdlog::level::trace}, - {"debug", spdlog::level::debug}, - {"info", spdlog::level::info}, - {"warn", spdlog::level::warn}, - {"warning", spdlog::level::warn}, - {"err", spdlog::level::err}, - {"error", spdlog::level::err}, - {"critical", spdlog::level::critical}, - {"off", spdlog::level::off}}; - - auto it = levelMap.find(level); - if (it != levelMap.end()) - return it->second; - - isUnknown = true; - return spdlog::level::info; -} -} // namespace - inline bool port_in_use(unsigned short port) { @@ -77,298 +47,30 @@ static std::string computeBufferMd5(const std::string& buffer) return oss.str(); } -std::string TeamServer::generateToken() const -{ - static constexpr char charset[] = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - - std::random_device rd; - std::mt19937 generator(rd()); - std::uniform_int_distribution distribution(0, sizeof(charset) - 2); - - std::string token(64, '\0'); - for (auto& ch : token) - { - ch = charset[distribution(generator)]; - } - - return token; -} - -std::string TeamServer::hashPassword(const std::string& password) const -{ - unsigned char hash[SHA256_DIGEST_LENGTH]; - SHA256_CTX ctx; - - SHA256_Init(&ctx); - SHA256_Update(&ctx, reinterpret_cast(password.data()), password.size()); - SHA256_Final(hash, &ctx); - - std::ostringstream oss; - for (size_t i = 0; i < SHA256_DIGEST_LENGTH; ++i) - { - oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(hash[i]); - } - - return oss.str(); -} - -void TeamServer::cleanupExpiredTokens() -{ - if (!m_authEnabled) - return; - - const auto now = std::chrono::steady_clock::now(); - std::lock_guard lock(m_authMutex); - for (auto it = m_activeTokens.begin(); it != m_activeTokens.end();) - { - if (now >= it->second) - { - it = m_activeTokens.erase(it); - } - else - { - ++it; - } - } -} - grpc::Status TeamServer::ensureAuthenticated(grpc::ServerContext* context) { - if (!m_authEnabled) - return grpc::Status::OK; - - const auto& metadata = context->client_metadata(); - auto metadataIt = metadata.find("authorization"); - if (metadataIt == metadata.end()) - { - m_logger->warn("gRPC request rejected: missing authorization metadata"); - return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Missing authorization metadata"); - } - - std::string authHeader(metadataIt->second.data(), metadataIt->second.length()); - static const std::string prefix = "Bearer "; - if (authHeader.rfind(prefix, 0) != 0) - { - m_logger->warn("gRPC request rejected: malformed authorization header"); - return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Malformed authorization header"); - } - - std::string token = authHeader.substr(prefix.size()); - - std::lock_guard lock(m_authMutex); - auto now = std::chrono::steady_clock::now(); - auto tokenIt = m_activeTokens.find(token); - if (tokenIt == m_activeTokens.end()) - { - m_logger->warn("gRPC request rejected: invalid token presented"); - return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Invalid token"); - } - - if (now >= tokenIt->second) - { - m_logger->warn("gRPC request rejected: expired token presented"); - m_activeTokens.erase(tokenIt); - return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Token expired"); - } - - tokenIt->second = now + m_tokenValidityDuration; - - return grpc::Status::OK; + return m_authManager->ensureAuthenticated(context->client_metadata()); } TeamServer::TeamServer(const nlohmann::json& config) - : m_config(config), m_isSocksServerRunning(false), m_isSocksServerBinded(false), m_authCredentialsFile(""), m_authEnabled(false), m_tokenValidityDuration(std::chrono::minutes(60)) + : m_config(config), m_isSocksServerRunning(false), m_isSocksServerBinded(false) { - // Logger - std::vector sinks; - - auto console_sink = std::make_shared(); - sinks.push_back(console_sink); - - auto file_sink = std::make_shared("logs/TeamServer.txt", 1024 * 1024 * 10, 3); - sinks.push_back(file_sink); - - std::string logLevel = "info"; - auto logLevelIt = config.find("LogLevel"); - if (logLevelIt != config.end() && logLevelIt->is_string()) - logLevel = logLevelIt->get(); - - bool isUnknownLogLevel = false; - spdlog::level::level_enum configuredLevel = parseLogLevel(logLevel, isUnknownLogLevel); - - console_sink->set_level(configuredLevel); - file_sink->set_level(configuredLevel); - - m_logger = std::make_shared("TeamServer", begin(sinks), end(sinks)); - - m_logger->set_level(configuredLevel); - m_logger->flush_on(spdlog::level::warn); - - if (isUnknownLogLevel) - m_logger->warn("Unknown log level '{}' requested, defaulting to 'info'.", logLevel); - - m_logger->debug("TeamServer logging initialized at {} level", spdlog::level::to_string_view(m_logger->level())); - - // Config directory - m_teamServerModulesDirectoryPath = config["TeamServerModulesDirectoryPath"].get(); - m_linuxModulesDirectoryPath = config["LinuxModulesDirectoryPath"].get(); - m_windowsModulesDirectoryPath = config["WindowsModulesDirectoryPath"].get(); - m_linuxBeaconsDirectoryPath = config["LinuxBeaconsDirectoryPath"].get(); - m_windowsBeaconsDirectoryPath = config["WindowsBeaconsDirectoryPath"].get(); - m_toolsDirectoryPath = config["ToolsDirectoryPath"].get(); - m_scriptsDirectoryPath = config["ScriptsDirectoryPath"].get(); - - auto authFileIt = config.find("AuthCredentialsFile"); - if (authFileIt != config.end() && authFileIt->is_string()) - { - m_authCredentialsFile = authFileIt->get(); - std::ifstream authFile(m_authCredentialsFile); - if (authFile.good()) - { - try - { - json authConfig = json::parse(authFile); - int ttlMinutes = authConfig.value("token_ttl_minutes", static_cast(m_tokenValidityDuration.count())); - if (ttlMinutes > 0) - { - m_tokenValidityDuration = std::chrono::minutes(ttlMinutes); - } + m_logger = createTeamServerLogger(config); - auto normalizeHash = [](std::string hash) - { - std::transform(hash.begin(), hash.end(), hash.begin(), [](unsigned char c) - { return static_cast(std::tolower(c)); }); - return hash; - }; - - m_userPasswordHashes.clear(); - - auto usersIt = authConfig.find("users"); - if (usersIt != authConfig.end()) - { - if (!usersIt->is_array()) - { - m_logger->error("Authentication credential file {0} has a 'users' entry that is not an array.", m_authCredentialsFile); - } - else - { - for (const auto& userEntry : *usersIt) - { - if (!userEntry.is_object()) - { - m_logger->warn("Skipping malformed user entry in {0}; expected an object.", m_authCredentialsFile); - continue; - } - - std::string username = userEntry.value("username", std::string()); - if (username.empty()) - { - m_logger->warn("Skipping user entry with missing username in {0}.", m_authCredentialsFile); - continue; - } + TeamServerRuntimeConfig runtimeConfig = TeamServerRuntimeConfig::fromJson(config); + m_teamServerModulesDirectoryPath = runtimeConfig.teamServerModulesDirectoryPath; + m_linuxModulesDirectoryPath = runtimeConfig.linuxModulesDirectoryPath; + m_windowsModulesDirectoryPath = runtimeConfig.windowsModulesDirectoryPath; + m_linuxBeaconsDirectoryPath = runtimeConfig.linuxBeaconsDirectoryPath; + m_windowsBeaconsDirectoryPath = runtimeConfig.windowsBeaconsDirectoryPath; + m_toolsDirectoryPath = runtimeConfig.toolsDirectoryPath; + m_scriptsDirectoryPath = runtimeConfig.scriptsDirectoryPath; - std::string passwordHash = normalizeHash(userEntry.value("password_hash", std::string())); - if (passwordHash.empty()) - { - std::string plaintextPassword = userEntry.value("password", std::string()); - if (!plaintextPassword.empty()) - { - m_logger->warn("User '{0}' in credentials file provides a plaintext password; hashing at startup but please update the file to store 'password_hash'.", username); - passwordHash = hashPassword(plaintextPassword); - } - } + runtimeConfig.validateDirectories(m_logger); + runtimeConfig.configureCommonCommands(m_commonCommands); - if (passwordHash.empty()) - { - m_logger->warn("Skipping user '{0}' in {1} due to missing password hash.", username, m_authCredentialsFile); - continue; - } - - m_userPasswordHashes[username] = passwordHash; - } - } - } - else - { - std::string username = authConfig.value("username", std::string()); - std::string passwordHash = normalizeHash(authConfig.value("password_hash", std::string())); - if (passwordHash.empty()) - { - std::string plaintextPassword = authConfig.value("password", std::string()); - if (!plaintextPassword.empty()) - { - m_logger->warn("Legacy credentials format detected in {0}; hashing plaintext password but please migrate to 'users' array with hashed passwords.", m_authCredentialsFile); - passwordHash = hashPassword(plaintextPassword); - } - } - - if (!username.empty() && !passwordHash.empty()) - { - m_userPasswordHashes[username] = passwordHash; - } - } - - if (!m_userPasswordHashes.empty()) - { - m_authEnabled = true; - m_logger->info("Authentication enabled for {0} user(s) using credentials file: {1}", m_userPasswordHashes.size(), m_authCredentialsFile); - } - else - { - m_logger->error("Authentication credential file {0} does not contain any valid user credentials.", m_authCredentialsFile); - } - } - catch (const std::exception& ex) - { - m_logger->error("Failed to parse authentication credential file {0}: {1}", m_authCredentialsFile, ex.what()); - } - } - else - { - m_logger->critical("Authentication credential file not found: {0}", m_authCredentialsFile); - } - } - else - { - m_logger->warn("AuthCredentialsFile entry missing from configuration. gRPC authentication is disabled."); - } - - fs::path checkPath = m_teamServerModulesDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("TeamServer modules directory path don't exist: {0}", m_teamServerModulesDirectoryPath.c_str()); - - checkPath = m_linuxModulesDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Linux modules directory path don't exist: {0}", m_linuxModulesDirectoryPath.c_str()); - - checkPath = m_windowsModulesDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Windows modules directory path don't exist: {0}", m_windowsModulesDirectoryPath.c_str()); - - checkPath = m_linuxBeaconsDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Linux beacon directory path don't exist: {0}", m_linuxBeaconsDirectoryPath.c_str()); - - checkPath = m_windowsBeaconsDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Windows beacon directory path don't exist: {0}", m_windowsBeaconsDirectoryPath.c_str()); - - checkPath = m_toolsDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Tools directory path don't exist: {0}", m_toolsDirectoryPath.c_str()); - - checkPath = m_scriptsDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Script directory path don't exist: {0}", m_scriptsDirectoryPath.c_str()); - - m_commonCommands.setDirectories(m_teamServerModulesDirectoryPath, - m_linuxModulesDirectoryPath, - m_windowsModulesDirectoryPath, - m_linuxBeaconsDirectoryPath, - m_windowsBeaconsDirectoryPath, - m_toolsDirectoryPath, - m_scriptsDirectoryPath); + m_authManager = std::make_unique(m_logger); + m_authManager->configure(config); // Modules m_logger->debug("TeamServer module directory path {0}", m_teamServerModulesDirectoryPath.c_str()); @@ -409,13 +111,7 @@ TeamServer::TeamServer(const nlohmann::json& config) std::unique_ptr moduleCmd_(moduleCmd); m_moduleCmd.push_back(std::move(moduleCmd_)); - m_moduleCmd.back()->setDirectories(m_teamServerModulesDirectoryPath, - m_linuxModulesDirectoryPath, - m_windowsModulesDirectoryPath, - m_linuxBeaconsDirectoryPath, - m_windowsBeaconsDirectoryPath, - m_toolsDirectoryPath, - m_scriptsDirectoryPath); + runtimeConfig.configureModule(*m_moduleCmd.back()); m_logger->debug("Module {0} loaded", entry.path().filename().c_str()); modulesLoaded++; @@ -449,49 +145,7 @@ TeamServer::~TeamServer() grpc::Status TeamServer::Authenticate(grpc::ServerContext* context, const teamserverapi::AuthRequest* request, teamserverapi::AuthResponse* response) { (void)context; - - if (!m_authEnabled) - { - response->set_status(teamserverapi::KO); - response->set_message("Authentication is not configured on the server"); - return grpc::Status::OK; - } - - cleanupExpiredTokens(); - - const std::string& username = request->username(); - const std::string& password = request->password(); - - auto userIt = m_userPasswordHashes.find(username); - if (userIt == m_userPasswordHashes.end()) - { - response->set_status(teamserverapi::KO); - response->set_message("Invalid credentials"); - m_logger->warn("Authentication failed for unknown user '{}'", username); - return grpc::Status::OK; - } - - std::string providedHash = hashPassword(password); - if (providedHash != userIt->second) - { - response->set_status(teamserverapi::KO); - response->set_message("Invalid credentials"); - m_logger->warn("Authentication failed due to incorrect password for user '{}'", username); - return grpc::Status::OK; - } - - std::string token = generateToken(); - { - std::lock_guard lock(m_authMutex); - m_activeTokens[token] = std::chrono::steady_clock::now() + m_tokenValidityDuration; - } - - response->set_status(teamserverapi::OK); - response->set_token(token); - response->set_message("Authentication successful"); - m_logger->info("User '{}' authenticated successfully", username); - - return grpc::Status::OK; + return m_authManager->authenticate(*request, *response); } // Get the list of liseteners from primary listeners @@ -2095,14 +1749,8 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser } std::unique_ptr moduleCmdPtr(moduleCmd); - moduleCmdPtr->setDirectories( - m_teamServerModulesDirectoryPath, - m_linuxModulesDirectoryPath, - m_windowsModulesDirectoryPath, - m_linuxBeaconsDirectoryPath, - m_windowsBeaconsDirectoryPath, - m_toolsDirectoryPath, - m_scriptsDirectoryPath); + TeamServerRuntimeConfig runtimeConfig = TeamServerRuntimeConfig::fromJson(m_config); + runtimeConfig.configureModule(*moduleCmdPtr); m_logger->debug("Module {0} loaded", entry.path().filename().c_str()); m_moduleCmd.push_back(std::move(moduleCmdPtr)); @@ -2404,124 +2052,3 @@ int TeamServer::prepMsg(const std::string& input, C2Message& c2Message, bool isW return res; } - -int main(int argc, char* argv[]) -{ - std::string configFile = "TeamServerConfig.json"; - if (argc >= 2) - { - configFile = argv[1]; - } - - // - // Logger - // - std::vector sinks; - - auto console_sink = std::make_shared(); - sinks.push_back(console_sink); - - auto file_sink = std::make_shared("logs/TeamServer.txt", 1024 * 1024 * 10, 3); - sinks.push_back(file_sink); - - std::unique_ptr logger = std::make_unique("TeamServer", begin(sinks), end(sinks)); - - // - // TeamServer Config - // - std::ifstream f(configFile); - - // Check if the file is successfully opened - if (!f.is_open()) - { - std::cerr << "Error: Config file '" << configFile << "' not found or could not be opened." << std::endl; - return 1; - } - - json config; - try - { - config = json::parse(f); - } - catch (const json::parse_error& e) - { - std::cerr << "Error: Failed to parse JSON in config file '" << configFile << "' - " << e.what() << std::endl; - return 1; - } - - std::string logLevel = "info"; - auto logLevelIt = config.find("LogLevel"); - if (logLevelIt != config.end() && logLevelIt->is_string()) - logLevel = logLevelIt->get(); - - bool isUnknownLogLevel = false; - spdlog::level::level_enum configuredLevel = parseLogLevel(logLevel, isUnknownLogLevel); - - console_sink->set_level(configuredLevel); - file_sink->set_level(configuredLevel); - logger->set_level(configuredLevel); - logger->flush_on(spdlog::level::warn); - - if (isUnknownLogLevel) - logger->warn("Unknown log level '{}' requested, defaulting to 'info'.", logLevel); - - logger->info("TeamServer logging initialized at {} level", spdlog::level::to_string_view(logger->level())); - - std::string serverGRPCAdd = config["ServerGRPCAdd"].get(); - std::string ServerGRPCPort = config["ServerGRPCPort"].get(); - std::string serverAddress = serverGRPCAdd; - serverAddress += ':'; - serverAddress += ServerGRPCPort; - - TeamServer service(config); - - // TSL Connection configuration - std::string servCrtFilePath = config["ServCrtFile"].get(); - std::ifstream servCrtFile(servCrtFilePath, std::ios::binary); - if (!servCrtFile.good()) - { - logger->critical("Server ceritifcat file not found."); - return -1; - } - std::string cert(std::istreambuf_iterator(servCrtFile), {}); - - std::string servKeyFilePath = config["ServKeyFile"].get(); - std::ifstream servKeyFile(servKeyFilePath, std::ios::binary); - if (!servKeyFile.good()) - { - logger->critical("Server key file not found."); - return -1; - } - std::string key(std::istreambuf_iterator(servKeyFile), {}); - - std::string rootCA = config["RootCA"].get(); - std::ifstream rootFile(rootCA, std::ios::binary); - if (!rootFile.good()) - { - logger->critical("Root CA file not found."); - return -1; - } - std::string root(std::istreambuf_iterator(rootFile), {}); - - grpc::SslServerCredentialsOptions::PemKeyCertPair keycert = - { - key, - cert}; - - grpc::SslServerCredentialsOptions sslOps; - sslOps.pem_root_certs = root; - sslOps.pem_key_cert_pairs.push_back(keycert); - - // Start GRPC Server - grpc::ServerBuilder builder; - builder.AddListeningPort(serverAddress, grpc::SslServerCredentials(sslOps)); - builder.RegisterService(&service); - builder.SetMaxSendMessageSize(1024 * 1024 * 1024); - builder.SetMaxMessageSize(1024 * 1024 * 1024); - builder.SetMaxReceiveMessageSize(1024 * 1024 * 1024); - std::unique_ptr server(builder.BuildAndStart()); - - logger->info("Team Server listening on {0}", serverAddress); - - server->Wait(); -} diff --git a/teamServer/teamServer/TeamServer.hpp b/teamServer/teamServer/TeamServer.hpp index cdfed32..fed81a4 100644 --- a/teamServer/teamServer/TeamServer.hpp +++ b/teamServer/teamServer/TeamServer.hpp @@ -28,6 +28,8 @@ #include "nlohmann/json.hpp" +class TeamServerAuthManager; + class TeamServer final : public teamserverapi::TeamServerApi::Service { @@ -57,9 +59,6 @@ class TeamServer final : public teamserverapi::TeamServerApi::Service private: grpc::Status ensureAuthenticated(grpc::ServerContext* context); - std::string generateToken() const; - std::string hashPassword(const std::string& password) const; - void cleanupExpiredTokens(); nlohmann::json m_config; @@ -96,10 +95,5 @@ class TeamServer final : public teamserverapi::TeamServerApi::Service std::vector m_sentC2Messages; - std::string m_authCredentialsFile; - std::unordered_map m_userPasswordHashes; - bool m_authEnabled; - std::unordered_map m_activeTokens; - std::chrono::minutes m_tokenValidityDuration; - mutable std::mutex m_authMutex; + std::unique_ptr m_authManager; }; diff --git a/teamServer/teamServer/TeamServerAuth.cpp b/teamServer/teamServer/TeamServerAuth.cpp new file mode 100644 index 0000000..a544f18 --- /dev/null +++ b/teamServer/teamServer/TeamServerAuth.cpp @@ -0,0 +1,287 @@ +#include "TeamServerAuth.hpp" + +#include +#include +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +namespace +{ +std::string normalizeHash(std::string hash) +{ + std::transform(hash.begin(), hash.end(), hash.begin(), [](unsigned char c) + { return static_cast(std::tolower(c)); }); + return hash; +} +} // namespace + +TeamServerAuthManager::TeamServerAuthManager(std::shared_ptr logger) + : m_logger(std::move(logger)), + m_authCredentialsFile(""), + m_authEnabled(false), + m_tokenValidityDuration(std::chrono::minutes(60)) +{ +} + +void TeamServerAuthManager::configure(const nlohmann::json& config) +{ + m_authCredentialsFile.clear(); + m_userPasswordHashes.clear(); + m_activeTokens.clear(); + m_authEnabled = false; + m_tokenValidityDuration = std::chrono::minutes(60); + + auto authFileIt = config.find("AuthCredentialsFile"); + if (authFileIt == config.end() || !authFileIt->is_string()) + { + m_logger->warn("AuthCredentialsFile entry missing from configuration. gRPC authentication is disabled."); + return; + } + + m_authCredentialsFile = authFileIt->get(); + std::ifstream authFile(m_authCredentialsFile); + if (!authFile.good()) + { + m_logger->critical("Authentication credential file not found: {0}", m_authCredentialsFile); + return; + } + + try + { + json authConfig = json::parse(authFile); + int ttlMinutes = authConfig.value("token_ttl_minutes", static_cast(m_tokenValidityDuration.count())); + if (ttlMinutes > 0) + { + m_tokenValidityDuration = std::chrono::minutes(ttlMinutes); + } + + auto usersIt = authConfig.find("users"); + if (usersIt != authConfig.end()) + { + if (!usersIt->is_array()) + { + m_logger->error("Authentication credential file {0} has a 'users' entry that is not an array.", m_authCredentialsFile); + } + else + { + for (const auto& userEntry : *usersIt) + { + if (!userEntry.is_object()) + { + m_logger->warn("Skipping malformed user entry in {0}; expected an object.", m_authCredentialsFile); + continue; + } + + std::string username = userEntry.value("username", std::string()); + if (username.empty()) + { + m_logger->warn("Skipping user entry with missing username in {0}.", m_authCredentialsFile); + continue; + } + + std::string passwordHash = normalizeHash(userEntry.value("password_hash", std::string())); + if (passwordHash.empty()) + { + std::string plaintextPassword = userEntry.value("password", std::string()); + if (!plaintextPassword.empty()) + { + m_logger->warn("User '{0}' in credentials file provides a plaintext password; hashing at startup but please update the file to store 'password_hash'.", username); + passwordHash = hashPassword(plaintextPassword); + } + } + + if (passwordHash.empty()) + { + m_logger->warn("Skipping user '{0}' in {1} due to missing password hash.", username, m_authCredentialsFile); + continue; + } + + m_userPasswordHashes[username] = passwordHash; + } + } + } + else + { + std::string username = authConfig.value("username", std::string()); + std::string passwordHash = normalizeHash(authConfig.value("password_hash", std::string())); + if (passwordHash.empty()) + { + std::string plaintextPassword = authConfig.value("password", std::string()); + if (!plaintextPassword.empty()) + { + m_logger->warn("Legacy credentials format detected in {0}; hashing plaintext password but please migrate to 'users' array with hashed passwords.", m_authCredentialsFile); + passwordHash = hashPassword(plaintextPassword); + } + } + + if (!username.empty() && !passwordHash.empty()) + { + m_userPasswordHashes[username] = passwordHash; + } + } + + if (!m_userPasswordHashes.empty()) + { + m_authEnabled = true; + m_logger->info("Authentication enabled for {0} user(s) using credentials file: {1}", m_userPasswordHashes.size(), m_authCredentialsFile); + } + else + { + m_logger->error("Authentication credential file {0} does not contain any valid user credentials.", m_authCredentialsFile); + } + } + catch (const std::exception& ex) + { + m_logger->error("Failed to parse authentication credential file {0}: {1}", m_authCredentialsFile, ex.what()); + } +} + +grpc::Status TeamServerAuthManager::authenticate(const teamserverapi::AuthRequest& request, teamserverapi::AuthResponse& response) +{ + if (!m_authEnabled) + { + response.set_status(teamserverapi::KO); + response.set_message("Authentication is not configured on the server"); + return grpc::Status::OK; + } + + cleanupExpiredTokens(); + + const std::string& username = request.username(); + const std::string& password = request.password(); + + auto userIt = m_userPasswordHashes.find(username); + if (userIt == m_userPasswordHashes.end()) + { + response.set_status(teamserverapi::KO); + response.set_message("Invalid credentials"); + m_logger->warn("Authentication failed for unknown user '{}'", username); + return grpc::Status::OK; + } + + std::string providedHash = hashPassword(password); + if (providedHash != userIt->second) + { + response.set_status(teamserverapi::KO); + response.set_message("Invalid credentials"); + m_logger->warn("Authentication failed due to incorrect password for user '{}'", username); + return grpc::Status::OK; + } + + std::string token = generateToken(); + { + std::lock_guard lock(m_authMutex); + m_activeTokens[token] = std::chrono::steady_clock::now() + m_tokenValidityDuration; + } + + response.set_status(teamserverapi::OK); + response.set_token(token); + response.set_message("Authentication successful"); + m_logger->info("User '{}' authenticated successfully", username); + + return grpc::Status::OK; +} + +grpc::Status TeamServerAuthManager::ensureAuthenticated(const std::multimap& metadata) +{ + if (!m_authEnabled) + return grpc::Status::OK; + + auto metadataIt = std::find_if(metadata.begin(), metadata.end(), [](const auto& entry) + { return entry.first == "authorization"; }); + if (metadataIt == metadata.end()) + { + m_logger->warn("gRPC request rejected: missing authorization metadata"); + return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Missing authorization metadata"); + } + + std::string authHeader(metadataIt->second.data(), metadataIt->second.length()); + static const std::string prefix = "Bearer "; + if (authHeader.rfind(prefix, 0) != 0) + { + m_logger->warn("gRPC request rejected: malformed authorization header"); + return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Malformed authorization header"); + } + + std::string token = authHeader.substr(prefix.size()); + + std::lock_guard lock(m_authMutex); + auto now = std::chrono::steady_clock::now(); + auto tokenIt = m_activeTokens.find(token); + if (tokenIt == m_activeTokens.end()) + { + m_logger->warn("gRPC request rejected: invalid token presented"); + return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Invalid token"); + } + + if (now >= tokenIt->second) + { + m_logger->warn("gRPC request rejected: expired token presented"); + m_activeTokens.erase(tokenIt); + return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Token expired"); + } + + tokenIt->second = now + m_tokenValidityDuration; + return grpc::Status::OK; +} + +std::string TeamServerAuthManager::generateToken() const +{ + static constexpr char charset[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + std::random_device rd; + std::mt19937 generator(rd()); + std::uniform_int_distribution distribution(0, sizeof(charset) - 2); + + std::string token(64, '\0'); + for (auto& ch : token) + { + ch = charset[distribution(generator)]; + } + + return token; +} + +std::string TeamServerAuthManager::hashPassword(const std::string& password) const +{ + unsigned char hash[SHA256_DIGEST_LENGTH]; + SHA256_CTX ctx; + + SHA256_Init(&ctx); + SHA256_Update(&ctx, reinterpret_cast(password.data()), password.size()); + SHA256_Final(hash, &ctx); + + std::ostringstream oss; + for (size_t i = 0; i < SHA256_DIGEST_LENGTH; ++i) + { + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(hash[i]); + } + + return oss.str(); +} + +void TeamServerAuthManager::cleanupExpiredTokens() +{ + if (!m_authEnabled) + return; + + const auto now = std::chrono::steady_clock::now(); + std::lock_guard lock(m_authMutex); + for (auto it = m_activeTokens.begin(); it != m_activeTokens.end();) + { + if (now >= it->second) + { + it = m_activeTokens.erase(it); + } + else + { + ++it; + } + } +} diff --git a/teamServer/teamServer/TeamServerAuth.hpp b/teamServer/teamServer/TeamServerAuth.hpp new file mode 100644 index 0000000..98b3346 --- /dev/null +++ b/teamServer/teamServer/TeamServerAuth.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "TeamServerApi.pb.h" +#include "nlohmann/json.hpp" +#include "spdlog/logger.h" + +class TeamServerAuthManager +{ +public: + explicit TeamServerAuthManager(std::shared_ptr logger); + + void configure(const nlohmann::json& config); + + grpc::Status authenticate(const teamserverapi::AuthRequest& request, teamserverapi::AuthResponse& response); + grpc::Status ensureAuthenticated(const std::multimap& metadata); + +private: + std::string generateToken() const; + std::string hashPassword(const std::string& password) const; + void cleanupExpiredTokens(); + + std::shared_ptr m_logger; + std::string m_authCredentialsFile; + std::unordered_map m_userPasswordHashes; + bool m_authEnabled; + std::unordered_map m_activeTokens; + std::chrono::minutes m_tokenValidityDuration; + mutable std::mutex m_authMutex; +}; diff --git a/teamServer/teamServer/TeamServerBootstrap.cpp b/teamServer/teamServer/TeamServerBootstrap.cpp new file mode 100644 index 0000000..eb8e1c2 --- /dev/null +++ b/teamServer/teamServer/TeamServerBootstrap.cpp @@ -0,0 +1,149 @@ +#include "TeamServerBootstrap.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include "TeamServer.hpp" +#include "spdlog/logger.h" +#include "spdlog/sinks/rotating_file_sink.h" +#include "spdlog/sinks/stdout_color_sinks.h" + +using json = nlohmann::json; + +namespace +{ +spdlog::level::level_enum parseLogLevel(std::string level, bool& isUnknown) +{ + isUnknown = false; + + std::transform(level.begin(), level.end(), level.begin(), + [](unsigned char c) + { return static_cast(std::tolower(c)); }); + + static const std::unordered_map levelMap = + { + {"trace", spdlog::level::trace}, + {"debug", spdlog::level::debug}, + {"info", spdlog::level::info}, + {"warn", spdlog::level::warn}, + {"warning", spdlog::level::warn}, + {"err", spdlog::level::err}, + {"error", spdlog::level::err}, + {"critical", spdlog::level::critical}, + {"off", spdlog::level::off}}; + + auto it = levelMap.find(level); + if (it != levelMap.end()) + return it->second; + + isUnknown = true; + return spdlog::level::info; +} + +std::string readRequiredFile(const std::string& filePath, const char* description, const std::shared_ptr& logger) +{ + std::ifstream input(filePath, std::ios::binary); + if (!input.good()) + { + logger->critical("{} not found.", description); + throw std::runtime_error(std::string(description) + " not found."); + } + + return std::string(std::istreambuf_iterator(input), {}); +} +} // namespace + +nlohmann::json loadTeamServerConfigFile(const std::string& configFile) +{ + std::ifstream input(configFile); + if (!input.is_open()) + { + throw std::runtime_error("Error: Config file '" + configFile + "' not found or could not be opened."); + } + + try + { + return json::parse(input); + } + catch (const json::parse_error& e) + { + throw std::runtime_error("Error: Failed to parse JSON in config file '" + configFile + "' - " + e.what()); + } +} + +std::shared_ptr createTeamServerLogger(const nlohmann::json& config) +{ + std::vector sinks; + + auto consoleSink = std::make_shared(); + auto fileSink = std::make_shared("logs/TeamServer.txt", 1024 * 1024 * 10, 3); + sinks.push_back(consoleSink); + sinks.push_back(fileSink); + + std::string logLevel = "info"; + auto logLevelIt = config.find("LogLevel"); + if (logLevelIt != config.end() && logLevelIt->is_string()) + logLevel = logLevelIt->get(); + + bool isUnknownLogLevel = false; + spdlog::level::level_enum configuredLevel = parseLogLevel(logLevel, isUnknownLogLevel); + + consoleSink->set_level(configuredLevel); + fileSink->set_level(configuredLevel); + + auto logger = std::make_shared("TeamServer", begin(sinks), end(sinks)); + logger->set_level(configuredLevel); + logger->flush_on(spdlog::level::warn); + + if (isUnknownLogLevel) + logger->warn("Unknown log level '{}' requested, defaulting to 'info'.", logLevel); + + logger->info("TeamServer logging initialized at {} level", spdlog::level::to_string_view(logger->level())); + return logger; +} + +TeamServerTlsMaterial loadTeamServerTlsMaterial(const nlohmann::json& config, const std::shared_ptr& logger) +{ + TeamServerTlsMaterial material; + material.certificate = readRequiredFile(config["ServCrtFile"].get(), "Server ceritifcat file", logger); + material.key = readRequiredFile(config["ServKeyFile"].get(), "Server key file", logger); + material.rootCertificate = readRequiredFile(config["RootCA"].get(), "Root CA file", logger); + return material; +} + +std::string buildTeamServerGrpcAddress(const nlohmann::json& config) +{ + std::string serverAddress = config["ServerGRPCAdd"].get(); + serverAddress += ':'; + serverAddress += config["ServerGRPCPort"].get(); + return serverAddress; +} + +std::unique_ptr buildAndStartTeamServerServer( + const nlohmann::json& config, + TeamServer& service, + const TeamServerTlsMaterial& tlsMaterial) +{ + grpc::SslServerCredentialsOptions::PemKeyCertPair keycert = + { + tlsMaterial.key, + tlsMaterial.certificate}; + + grpc::SslServerCredentialsOptions sslOptions; + sslOptions.pem_root_certs = tlsMaterial.rootCertificate; + sslOptions.pem_key_cert_pairs.push_back(keycert); + + grpc::ServerBuilder builder; + builder.AddListeningPort(buildTeamServerGrpcAddress(config), grpc::SslServerCredentials(sslOptions)); + builder.RegisterService(&service); + builder.SetMaxSendMessageSize(1024 * 1024 * 1024); + builder.SetMaxMessageSize(1024 * 1024 * 1024); + builder.SetMaxReceiveMessageSize(1024 * 1024 * 1024); + return builder.BuildAndStart(); +} diff --git a/teamServer/teamServer/TeamServerBootstrap.hpp b/teamServer/teamServer/TeamServerBootstrap.hpp new file mode 100644 index 0000000..6796158 --- /dev/null +++ b/teamServer/teamServer/TeamServerBootstrap.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include +#include + +#include "nlohmann/json.hpp" + +namespace spdlog +{ +class logger; +} + +class TeamServer; + +struct TeamServerTlsMaterial +{ + std::string certificate; + std::string key; + std::string rootCertificate; +}; + +nlohmann::json loadTeamServerConfigFile(const std::string& configFile); +std::shared_ptr createTeamServerLogger(const nlohmann::json& config); +TeamServerTlsMaterial loadTeamServerTlsMaterial(const nlohmann::json& config, const std::shared_ptr& logger); +std::string buildTeamServerGrpcAddress(const nlohmann::json& config); +std::unique_ptr buildAndStartTeamServerServer( + const nlohmann::json& config, + TeamServer& service, + const TeamServerTlsMaterial& tlsMaterial); diff --git a/teamServer/teamServer/TeamServerRuntimeConfig.cpp b/teamServer/teamServer/TeamServerRuntimeConfig.cpp new file mode 100644 index 0000000..5dec4b2 --- /dev/null +++ b/teamServer/teamServer/TeamServerRuntimeConfig.cpp @@ -0,0 +1,70 @@ +#include "TeamServerRuntimeConfig.hpp" + +#include + +#include "modules/ModuleCmd/CommonCommand.hpp" +#include "modules/ModuleCmd/ModuleCmd.hpp" +#include "spdlog/logger.h" + +namespace fs = std::filesystem; + +TeamServerRuntimeConfig TeamServerRuntimeConfig::fromJson(const nlohmann::json& config) +{ + TeamServerRuntimeConfig runtimeConfig; + runtimeConfig.teamServerModulesDirectoryPath = config["TeamServerModulesDirectoryPath"].get(); + runtimeConfig.linuxModulesDirectoryPath = config["LinuxModulesDirectoryPath"].get(); + runtimeConfig.windowsModulesDirectoryPath = config["WindowsModulesDirectoryPath"].get(); + runtimeConfig.linuxBeaconsDirectoryPath = config["LinuxBeaconsDirectoryPath"].get(); + runtimeConfig.windowsBeaconsDirectoryPath = config["WindowsBeaconsDirectoryPath"].get(); + runtimeConfig.toolsDirectoryPath = config["ToolsDirectoryPath"].get(); + runtimeConfig.scriptsDirectoryPath = config["ScriptsDirectoryPath"].get(); + return runtimeConfig; +} + +void TeamServerRuntimeConfig::validateDirectories(const std::shared_ptr& logger) const +{ + if (!fs::exists(teamServerModulesDirectoryPath)) + logger->error("TeamServer modules directory path don't exist: {0}", teamServerModulesDirectoryPath.c_str()); + + if (!fs::exists(linuxModulesDirectoryPath)) + logger->error("Linux modules directory path don't exist: {0}", linuxModulesDirectoryPath.c_str()); + + if (!fs::exists(windowsModulesDirectoryPath)) + logger->error("Windows modules directory path don't exist: {0}", windowsModulesDirectoryPath.c_str()); + + if (!fs::exists(linuxBeaconsDirectoryPath)) + logger->error("Linux beacon directory path don't exist: {0}", linuxBeaconsDirectoryPath.c_str()); + + if (!fs::exists(windowsBeaconsDirectoryPath)) + logger->error("Windows beacon directory path don't exist: {0}", windowsBeaconsDirectoryPath.c_str()); + + if (!fs::exists(toolsDirectoryPath)) + logger->error("Tools directory path don't exist: {0}", toolsDirectoryPath.c_str()); + + if (!fs::exists(scriptsDirectoryPath)) + logger->error("Script directory path don't exist: {0}", scriptsDirectoryPath.c_str()); +} + +void TeamServerRuntimeConfig::configureCommonCommands(CommonCommands& commonCommands) const +{ + commonCommands.setDirectories( + teamServerModulesDirectoryPath, + linuxModulesDirectoryPath, + windowsModulesDirectoryPath, + linuxBeaconsDirectoryPath, + windowsBeaconsDirectoryPath, + toolsDirectoryPath, + scriptsDirectoryPath); +} + +void TeamServerRuntimeConfig::configureModule(ModuleCmd& module) const +{ + module.setDirectories( + teamServerModulesDirectoryPath, + linuxModulesDirectoryPath, + windowsModulesDirectoryPath, + linuxBeaconsDirectoryPath, + windowsBeaconsDirectoryPath, + toolsDirectoryPath, + scriptsDirectoryPath); +} diff --git a/teamServer/teamServer/TeamServerRuntimeConfig.hpp b/teamServer/teamServer/TeamServerRuntimeConfig.hpp new file mode 100644 index 0000000..08b4a7c --- /dev/null +++ b/teamServer/teamServer/TeamServerRuntimeConfig.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "nlohmann/json.hpp" + +class CommonCommands; +class ModuleCmd; +namespace spdlog +{ +class logger; +} + +struct TeamServerRuntimeConfig +{ + std::string teamServerModulesDirectoryPath; + std::string linuxModulesDirectoryPath; + std::string windowsModulesDirectoryPath; + std::string linuxBeaconsDirectoryPath; + std::string windowsBeaconsDirectoryPath; + std::string toolsDirectoryPath; + std::string scriptsDirectoryPath; + + static TeamServerRuntimeConfig fromJson(const nlohmann::json& config); + + void validateDirectories(const std::shared_ptr& logger) const; + void configureCommonCommands(CommonCommands& commonCommands) const; + void configureModule(ModuleCmd& module) const; +}; diff --git a/teamServer/teamServer/main.cpp b/teamServer/teamServer/main.cpp new file mode 100644 index 0000000..2c05bb7 --- /dev/null +++ b/teamServer/teamServer/main.cpp @@ -0,0 +1,32 @@ +#include + +#include "TeamServer.hpp" +#include "TeamServerBootstrap.hpp" + +int main(int argc, char* argv[]) +{ + std::string configFile = "TeamServerConfig.json"; + if (argc >= 2) + { + configFile = argv[1]; + } + + try + { + auto config = loadTeamServerConfigFile(configFile); + auto logger = createTeamServerLogger(config); + + TeamServer service(config); + TeamServerTlsMaterial tlsMaterial = loadTeamServerTlsMaterial(config, logger); + auto server = buildAndStartTeamServerServer(config, service, tlsMaterial); + + logger->info("Team Server listening on {0}", buildTeamServerGrpcAddress(config)); + server->Wait(); + return 0; + } + catch (const std::exception& ex) + { + std::cerr << ex.what() << std::endl; + return 1; + } +} diff --git a/teamServer/tests/testsTestServer.cpp b/teamServer/tests/testsTestServer.cpp index 0af41aa..d6b916c 100644 --- a/teamServer/tests/testsTestServer.cpp +++ b/teamServer/tests/testsTestServer.cpp @@ -1,9 +1,176 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "TeamServerAuth.hpp" +#include "TeamServerBootstrap.hpp" +namespace fs = std::filesystem; + +namespace +{ +class ScopedPath +{ +public: + explicit ScopedPath(fs::path path) + : m_path(std::move(path)) + { + } + + ~ScopedPath() + { + std::error_code ec; + fs::remove_all(m_path, ec); + } + + const fs::path& path() const + { + return m_path; + } + +private: + fs::path m_path; +}; + +fs::path makeTempDirectory(const std::string& name) +{ + fs::path root = fs::temp_directory_path() / ("c2teamserver-" + name + "-" + std::to_string(::getpid())); + fs::create_directories(root); + return root; +} + +void writeFile(const fs::path& path, const std::string& content) +{ + std::ofstream output(path, std::ios::binary); + output << content; +} + +void testLoadConfigFile() +{ + ScopedPath tempDir(makeTempDirectory("config")); + fs::path validConfig = tempDir.path() / "config.json"; + writeFile(validConfig, R"({"ServerGRPCAdd":"127.0.0.1","ServerGRPCPort":"50051"})"); + + auto loadedConfig = loadTeamServerConfigFile(validConfig.string()); + assert(loadedConfig["ServerGRPCAdd"] == "127.0.0.1"); + assert(loadedConfig["ServerGRPCPort"] == "50051"); + + bool missingFileRaised = false; + try + { + (void)loadTeamServerConfigFile((tempDir.path() / "missing.json").string()); + } + catch (const std::runtime_error&) + { + missingFileRaised = true; + } + assert(missingFileRaised); + + fs::path invalidConfig = tempDir.path() / "invalid.json"; + writeFile(invalidConfig, "{not-json"); + + bool invalidJsonRaised = false; + try + { + (void)loadTeamServerConfigFile(invalidConfig.string()); + } + catch (const std::runtime_error&) + { + invalidJsonRaised = true; + } + assert(invalidJsonRaised); +} + +void testLoadTlsMaterial() +{ + ScopedPath tempDir(makeTempDirectory("tls")); + fs::path cert = tempDir.path() / "server.crt"; + fs::path key = tempDir.path() / "server.key"; + fs::path root = tempDir.path() / "root.crt"; + writeFile(cert, "CERT"); + writeFile(key, "KEY"); + writeFile(root, "ROOT"); + + nlohmann::json config = { + {"ServCrtFile", cert.string()}, + {"ServKeyFile", key.string()}, + {"RootCA", root.string()}, + {"LogLevel", "off"}}; + + auto logger = createTeamServerLogger(config); + auto tlsMaterial = loadTeamServerTlsMaterial(config, logger); + + assert(tlsMaterial.certificate == "CERT"); + assert(tlsMaterial.key == "KEY"); + assert(tlsMaterial.rootCertificate == "ROOT"); + assert(buildTeamServerGrpcAddress({ + {"ServerGRPCAdd", "127.0.0.1"}, + {"ServerGRPCPort", "50051"}}) == "127.0.0.1:50051"); +} + +void testAuthManagerRoundTrip() +{ + ScopedPath tempDir(makeTempDirectory("auth")); + fs::path credentials = tempDir.path() / "auth.json"; + writeFile(credentials, R"({ + "token_ttl_minutes": 10, + "users": [ + { + "username": "operator", + "password": "secret" + } + ] + })"); + + nlohmann::json config = { + {"AuthCredentialsFile", credentials.string()}, + {"LogLevel", "off"}}; + + auto logger = createTeamServerLogger(config); + TeamServerAuthManager authManager(logger); + authManager.configure(config); + + teamserverapi::AuthRequest validRequest; + validRequest.set_username("operator"); + validRequest.set_password("secret"); + + teamserverapi::AuthResponse validResponse; + assert(authManager.authenticate(validRequest, validResponse).ok()); + assert(validResponse.status() == teamserverapi::OK); + assert(!validResponse.token().empty()); + + std::string metadataKey = "authorization"; + std::string metadataValue = "Bearer " + validResponse.token(); + std::multimap metadata; + metadata.emplace( + grpc::string_ref(metadataKey.data(), metadataKey.size()), + grpc::string_ref(metadataValue.data(), metadataValue.size())); + + assert(authManager.ensureAuthenticated(metadata).ok()); + + teamserverapi::AuthRequest invalidRequest; + invalidRequest.set_username("operator"); + invalidRequest.set_password("wrong"); + + teamserverapi::AuthResponse invalidResponse; + assert(authManager.authenticate(invalidRequest, invalidResponse).ok()); + assert(invalidResponse.status() == teamserverapi::KO); + + std::multimap missingMetadata; + assert(!authManager.ensureAuthenticated(missingMetadata).ok()); +} +} // namespace int main() { + testLoadConfigFile(); + testLoadTlsMaterial(); + testAuthManagerRoundTrip(); return 0; -} \ No newline at end of file +} From f1eee012d6507e4a2c75a9ef8323815223b203dd Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 15:27:57 +0200 Subject: [PATCH 10/28] Split --- teamServer/CMakeLists.txt | 28 + teamServer/teamServer/TeamServer.cpp | 690 +----------------- teamServer/teamServer/TeamServer.hpp | 2 + .../TeamServerListenerSessionService.cpp | 639 ++++++++++++++++ .../TeamServerListenerSessionService.hpp | 64 ++ .../TeamServerListenerSessionServiceTests.cpp | 215 ++++++ 6 files changed, 977 insertions(+), 661 deletions(-) create mode 100644 teamServer/teamServer/TeamServerListenerSessionService.cpp create mode 100644 teamServer/teamServer/TeamServerListenerSessionService.hpp create mode 100644 teamServer/tests/TeamServerListenerSessionServiceTests.cpp diff --git a/teamServer/CMakeLists.txt b/teamServer/CMakeLists.txt index 43dee40..236d0f6 100644 --- a/teamServer/CMakeLists.txt +++ b/teamServer/CMakeLists.txt @@ -7,6 +7,7 @@ teamServer/TeamServer.cpp teamServer/TeamServerAuth.cpp teamServer/TeamServerRuntimeConfig.cpp teamServer/TeamServerBootstrap.cpp +teamServer/TeamServerListenerSessionService.cpp teamServer/main.cpp ../core/listener/Listener.cpp ../core/listener/ListenerTcp.cpp @@ -52,4 +53,31 @@ if(WITH_TESTS) $ "${C2_TEST_BIN_OUTPUT_DIR}/$") add_test(NAME testsTestServer COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") + + add_executable(testsTeamServerListenerSessionService + tests/TeamServerListenerSessionServiceTests.cpp + teamServer/TeamServerListenerSessionService.cpp + ../core/listener/Listener.cpp + ../core/listener/ListenerTcp.cpp + ../core/listener/ListenerHttp.cpp + ../core/listener/ListenerGithub.cpp + ../core/listener/ListenerDns.cpp + ../../thirdParty/base64/base64.cpp + ) + target_include_directories(testsTeamServerListenerSessionService PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/teamServer + ${CMAKE_SOURCE_DIR}/libs/libSocks5/src + ${CMAKE_SOURCE_DIR}/libs/libSocketHandler/src + ${CMAKE_SOURCE_DIR}/libs/libDns/src + ) + if(WIN32) + target_link_libraries(testsTeamServerListenerSessionService Dnscommunication SocketHandler GrpcMessages openssl::openssl ${OPENSSL_CRYPTO_LIBRARY} ZLIB::ZLIB grpc::grpc spdlog::spdlog) + else() + target_link_libraries(testsTeamServerListenerSessionService Dnscommunication SocketHandler GrpcMessages pthread openssl::openssl ZLIB::ZLIB grpc::grpc spdlog::spdlog httplib::httplib Crow::Crow dl rt) + endif() + + add_custom_command(TARGET testsTeamServerListenerSessionService POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $ "${C2_TEST_BIN_OUTPUT_DIR}/$") + + add_test(NAME testsTeamServerListenerSessionService COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") endif() diff --git a/teamServer/teamServer/TeamServer.cpp b/teamServer/teamServer/TeamServer.cpp index 3cbbb07..508c80b 100644 --- a/teamServer/teamServer/TeamServer.cpp +++ b/teamServer/teamServer/TeamServer.cpp @@ -2,6 +2,7 @@ #include "TeamServerAuth.hpp" #include "TeamServerBootstrap.hpp" +#include "TeamServerListenerSessionService.hpp" #include "TeamServerRuntimeConfig.hpp" #include @@ -13,7 +14,6 @@ #include #include #include -#include using namespace std; using namespace std::placeholders; @@ -29,24 +29,6 @@ inline bool port_in_use(unsigned short port) return 0; } -static std::string computeBufferMd5(const std::string& buffer) -{ - if (buffer.empty()) - return ""; - - unsigned char result[MD5_DIGEST_LENGTH]; - MD5_CTX ctx; - MD5_Init(&ctx); - MD5_Update(&ctx, buffer.data(), buffer.size()); - MD5_Final(result, &ctx); - - std::ostringstream oss; - for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) - oss << std::hex << std::setw(2) << std::setfill('0') << (int)result[i]; - - return oss.str(); -} - grpc::Status TeamServer::ensureAuthenticated(grpc::ServerContext* context) { return m_authManager->ensureAuthenticated(context->client_metadata()); @@ -71,6 +53,17 @@ TeamServer::TeamServer(const nlohmann::json& config) m_authManager = std::make_unique(m_logger); m_authManager->configure(config); + m_listenerSessionService = std::make_unique( + m_logger, + m_config, + m_listeners, + m_moduleCmd, + m_commonCommands, + m_cmdResponses, + m_sentResponses, + m_sentC2Messages, + [this](const std::string& input, C2Message& c2Message, bool isWindows) + { return this->prepMsg(input, c2Message, isWindows); }); // Modules m_logger->debug("TeamServer module directory path {0}", m_teamServerModulesDirectoryPath.c_str()); @@ -152,86 +145,12 @@ grpc::Status TeamServer::Authenticate(grpc::ServerContext* context, const teamse // and from listeners runing on beacon through sessionListener grpc::Status TeamServer::GetListeners(grpc::ServerContext* context, const teamserverapi::Empty* empty, grpc::ServerWriter* writer) { + (void)empty; auto authStatus = ensureAuthenticated(context); if (!authStatus.ok()) return authStatus; - - m_logger->trace("GetListeners"); - - for (int i = 0; i < m_listeners.size(); i++) - { - // For each primary listeners get the informations - teamserverapi::Listener listener; - listener.set_listenerhash(m_listeners[i]->getListenerHash()); - - std::string type = m_listeners[i]->getType(); - listener.set_type(type); - if (type == ListenerHttpType || type == ListenerHttpsType) - { - listener.set_ip(m_listeners[i]->getParam1()); - listener.set_port(std::stoi(m_listeners[i]->getParam2())); - } - else if (type == ListenerTcpType) - { - listener.set_ip(m_listeners[i]->getParam1()); - listener.set_port(std::stoi(m_listeners[i]->getParam2())); - } - else if (type == ListenerSmbType) - { - listener.set_ip(m_listeners[i]->getParam1()); - listener.set_domain(m_listeners[i]->getParam2()); - } - else if (type == ListenerGithubType) - { - listener.set_project(m_listeners[i]->getParam1()); - listener.set_token(m_listeners[i]->getParam2()); - } - else if (type == ListenerDnsType) - { - listener.set_domain(m_listeners[i]->getParam1()); - listener.set_port(std::stoi(m_listeners[i]->getParam2())); - } - listener.set_numberofsession(m_listeners[i]->getNumberOfSession()); - - writer->Write(listener); - - // check for each sessions alive from this listener check if their is listeners - int nbSession = m_listeners[i]->getNumberOfSession(); - for (int kk = 0; kk < nbSession; kk++) - { - std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - - if (!session->isSessionKilled()) - { - for (auto it = session->getListener().begin(); it != session->getListener().end(); ++it) - { - m_logger->trace("|-> sessionListenerList {0} {1} {0}", it->getType(), it->getParam1(), it->getParam2()); - - teamserverapi::Listener listener; - listener.set_listenerhash(it->getListenerHash()); - listener.set_beaconhash(session->getBeaconHash()); - std::string type = it->getType(); - listener.set_type(type); - if (type == ListenerTcpType) - { - listener.set_ip(it->getParam1()); - listener.set_port(std::stoi(it->getParam2())); - } - else if (type == ListenerSmbType) - { - listener.set_ip(it->getParam1()); - listener.set_domain(it->getParam2()); - } - - writer->Write(listener); - } - } - } - } - - m_logger->trace("GetListeners end"); - - return grpc::Status::OK; + return m_listenerSessionService->streamListeners([&](const teamserverapi::Listener& listener) + { return writer->Write(listener); }); } // Add listener that will run on the C2 @@ -241,163 +160,7 @@ grpc::Status TeamServer::AddListener(grpc::ServerContext* context, const teamser auto authStatus = ensureAuthenticated(context); if (!authStatus.ok()) return authStatus; - - m_logger->trace("AddListener"); - string type = listenerToCreate->type(); - - // check if the listener already existe - if (type == ListenerGithubType) - { - std::vector>::iterator object = - find_if(m_listeners.begin(), m_listeners.end(), - [&](shared_ptr& obj) - { - return (obj->getType() == listenerToCreate->type() && - obj->getParam1() == listenerToCreate->project() && - obj->getParam2() == listenerToCreate->token()); - }); - - if (object != m_listeners.end()) - { - m_logger->warn("Add listener failed: Listener already exist"); - return grpc::Status::OK; - } - } - else if (type == ListenerDnsType) - { - std::string domain = listenerToCreate->domain(); - int port = listenerToCreate->port(); - - // 🔸 Check if a DNS listener with the same domain and port already exists - auto existingDns = std::find_if( - m_listeners.begin(), - m_listeners.end(), - [&](std::shared_ptr& obj) - { - return (obj->getType() == ListenerDnsType && - obj->getParam1() == domain && - obj->getParam2() == std::to_string(port)); - }); - - if (existingDns != m_listeners.end()) - { - m_logger->warn("Add listener failed: DNS listener already running on {0}:{1}", domain, std::to_string(port)); - return grpc::Status::OK; - } - } - else - { - std::vector>::iterator object = - find_if(m_listeners.begin(), m_listeners.end(), - [&](shared_ptr& obj) - { - return (obj->getType() == listenerToCreate->type() && - obj->getParam1() == listenerToCreate->ip() && - obj->getParam2() == std::to_string(listenerToCreate->port())); - }); - - if (object != m_listeners.end()) - { - m_logger->warn("Add listener failed: Listener already exist"); - return grpc::Status::OK; - } - } - - // TODO use a init to check if the listener is correctly init without using excpetion in the constructor - if (type == ListenerTcpType) - { - int localPort = listenerToCreate->port(); - string localHost = listenerToCreate->ip(); - std::shared_ptr listenerTcp = make_shared(localHost, localPort, m_config); - int ret = listenerTcp->init(); - if (ret > 0) - { - listenerTcp->setIsPrimary(); - m_listeners.push_back(std::move(listenerTcp)); - - m_logger->info("AddListener Tcp {0}:{1}", localHost, std::to_string(localPort)); - } - else - { - m_logger->error("Error: AddListener Tcp {0}:{1}", localHost, std::to_string(localPort)); - } - } - else if (type == ListenerHttpType) - { - int localPort = listenerToCreate->port(); - string localHost = listenerToCreate->ip(); - std::shared_ptr listenerHttp = make_shared(localHost, localPort, m_config, false); - int ret = listenerHttp->init(); - if (ret > 0) - { - listenerHttp->setIsPrimary(); - m_listeners.push_back(std::move(listenerHttp)); - - m_logger->info("AddListener Http {0}:{1}", localHost, std::to_string(localPort)); - } - else - { - m_logger->error("Error: AddListener Http {0}:{1}", localHost, std::to_string(localPort)); - } - } - else if (type == ListenerHttpsType) - { - int localPort = listenerToCreate->port(); - string localHost = listenerToCreate->ip(); - std::shared_ptr listenerHttps = make_shared(localHost, localPort, m_config, true); - int ret = listenerHttps->init(); - if (ret > 0) - { - listenerHttps->setIsPrimary(); - m_listeners.push_back(std::move(listenerHttps)); - - m_logger->info("AddListener Https {0}:{1}", localHost, std::to_string(localPort)); - } - else - { - m_logger->error("Error: AddListener Https {0}:{1}", localHost, std::to_string(localPort)); - } - } - else if (type == ListenerGithubType) - { - std::string token = listenerToCreate->token(); - std::string project = listenerToCreate->project(); - std::shared_ptr listenerGithub = make_shared(project, token, m_config); - listenerGithub->setIsPrimary(); - m_listeners.push_back(std::move(listenerGithub)); - - m_logger->info("AddListener Github {0}:{1}", project, token); - } - else if (type == ListenerDnsType) - { - std::string domain = listenerToCreate->domain(); - int port = listenerToCreate->port(); - std::shared_ptr listenerDns = make_shared(domain, port, m_config); - listenerDns->setIsPrimary(); - m_listeners.push_back(std::move(listenerDns)); - - m_logger->info("AddListener Dns {0}:{1}", domain, std::to_string(port)); - } - - m_logger->trace("AddListener End"); - - return grpc::Status::OK; -} - -std::string generateUUID8() -{ - const char charset[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - const size_t length = 8; - std::random_device rd; - std::mt19937 generator(rd()); - std::uniform_int_distribution<> distribution(0, sizeof(charset) - 2); - - std::string uuid; - for (size_t i = 0; i < length; ++i) - { - uuid += charset[distribution(generator)]; - } - return uuid; + return m_listenerSessionService->addListener(*listenerToCreate); } grpc::Status TeamServer::StopListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToStop, teamserverapi::Response* response) @@ -405,172 +168,24 @@ grpc::Status TeamServer::StopListener(grpc::ServerContext* context, const teamse auto authStatus = ensureAuthenticated(context); if (!authStatus.ok()) return authStatus; - - m_logger->trace("StopListener"); - - // Stop primary listener - std::string listenerHash = listenerToStop->listenerhash(); - bool removedPrimary = false; - bool stopCommandSent = false; - - std::vector>::iterator object = - find_if(m_listeners.begin(), m_listeners.end(), - [&](shared_ptr& obj) - { return obj->getListenerHash() == listenerHash; }); - - if (object != m_listeners.end()) - { - m_listeners.erase(std::remove(m_listeners.begin(), m_listeners.end(), *object)); - removedPrimary = true; - } - - // Stop listerners runing on beacon by sending a messsage to this beacon - for (int i = 0; i < m_listeners.size(); i++) - { - int nbSession = m_listeners[i]->getNumberOfSession(); - for (int kk = 0; kk < nbSession; kk++) - { - std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - std::vector sessionListener = session->getListener(); - for (int j = 0; j < sessionListener.size(); j++) - { - if (listenerHash == sessionListener[j].getListenerHash()) - { - std::string input = "listener stop "; - input += sessionListener[j].getListenerHash(); - std::string beaconHash = session->getBeaconHash(); - - if (!input.empty()) - { - C2Message c2Message; - int res = prepMsg(input, c2Message); - - // echec init message - if (res != 0) - { - std::string hint = c2Message.returnvalue(); - - response->set_message(hint); - response->set_status(teamserverapi::KO); - } - - // Set the uuid to track the message and correlate with the response to get a clean output in the client - if (!c2Message.instruction().empty()) - { - std::string uuid = generateUUID8(); - c2Message.set_uuid(uuid); - m_listeners[i]->queueTask(beaconHash, c2Message); - - c2Message.set_cmd(input); - c2Message.set_data(""); - m_sentC2Messages.push_back(std::move(c2Message)); - stopCommandSent = true; - } - } - } - } - } - } - - if (removedPrimary || stopCommandSent) - m_logger->info("StopListener completed for {0} (primary removed: {1}, stop commands sent: {2})", - listenerHash, - removedPrimary ? "yes" : "no", - stopCommandSent ? "yes" : "no"); - else - m_logger->warn("StopListener request ignored: listener {0} not found", listenerHash); - - m_logger->trace("StopListener End"); - - return grpc::Status::OK; + return m_listenerSessionService->stopListener(*listenerToStop, response); } bool TeamServer::isListenerAlive(const std::string& listenerHash) { - m_logger->trace("isListenerAlive"); - - bool result = false; - for (int i = 0; i < m_listeners.size(); i++) - { - if (m_listeners[i]->getListenerHash() == listenerHash) - { - result = true; - return result; - } - - // check for each sessions alive from this listener check if their is listeners - int nbSession = m_listeners[i]->getNumberOfSession(); - for (int kk = 0; kk < nbSession; kk++) - { - std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - - std::vector sessionListenerList; - if (!session->isSessionKilled()) - { - sessionListenerList.insert(sessionListenerList.end(), session->getListener().begin(), session->getListener().end()); - - for (int j = 0; j < sessionListenerList.size(); j++) - { - if (sessionListenerList[j].getListenerHash() == listenerHash) - { - result = true; - return result; - } - } - } - } - } - - m_logger->trace("isListenerAlive end"); - - return result; + return m_listenerSessionService->isListenerAlive(listenerHash); } // Get the list of sessions on the primary listeners // Primary listers old all the information about beacons linked to themeself and linked to beacon listerners grpc::Status TeamServer::GetSessions(grpc::ServerContext* context, const teamserverapi::Empty* empty, grpc::ServerWriter* writer) { + (void)empty; auto authStatus = ensureAuthenticated(context); if (!authStatus.ok()) return authStatus; - - m_logger->trace("GetSessions"); - - for (int i = 0; i < m_listeners.size(); i++) - { - m_logger->trace("Listener {0}", m_listeners[i]->getListenerHash()); - - int nbSession = m_listeners[i]->getNumberOfSession(); - for (int kk = 0; kk < nbSession; kk++) - { - std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - - m_logger->trace("Session {0} From {1} {2}", session->getBeaconHash(), session->getListenerHash(), session->getLastProofOfLife()); - - teamserverapi::Session sessionTmp; - sessionTmp.set_listenerhash(session->getListenerHash()); - sessionTmp.set_beaconhash(session->getBeaconHash()); - sessionTmp.set_hostname(session->getHostname()); - sessionTmp.set_username(session->getUsername()); - sessionTmp.set_arch(session->getArch()); - sessionTmp.set_privilege(session->getPrivilege()); - sessionTmp.set_os(session->getOs()); - sessionTmp.set_lastproofoflife(session->getLastProofOfLife()); - sessionTmp.set_killed(session->isSessionKilled()); - sessionTmp.set_internalips(session->getInternalIps()); - sessionTmp.set_processid(session->getProcessId()); - sessionTmp.set_additionalinformation(session->getAdditionalInformation()); - - bool result = isListenerAlive(session->getListenerHash()); - - if (!session->isSessionKilled() && result) - writer->Write(sessionTmp); - } - } - - m_logger->trace("GetSessions end"); - - return grpc::Status::OK; + return m_listenerSessionService->streamSessions([&](const teamserverapi::Session& session) + { return writer->Write(session); }); } grpc::Status TeamServer::StopSession(grpc::ServerContext* context, const teamserverapi::Session* sessionToStop, teamserverapi::Response* response) @@ -578,46 +193,7 @@ grpc::Status TeamServer::StopSession(grpc::ServerContext* context, const teamser auto authStatus = ensureAuthenticated(context); if (!authStatus.ok()) return authStatus; - - m_logger->trace("StopSession"); - - std::string beaconHash = sessionToStop->beaconhash(); - std::string listenerHash = sessionToStop->listenerhash(); - - if (beaconHash.size() == SizeBeaconHash) - { - for (int i = 0; i < m_listeners.size(); i++) - { - if (m_listeners[i]->isSessionExist(beaconHash, listenerHash)) - { - C2Message c2Message; - int res = prepMsg(EndInstruction, c2Message); - - if (res != 0) - { - std::string hint = c2Message.returnvalue(); - - response->set_message(hint); - response->set_status(teamserverapi::KO); - } - - if (!c2Message.instruction().empty()) - { - m_listeners[i]->queueTask(beaconHash, c2Message); - m_listeners[i]->markSessionKilled(beaconHash); - m_logger->info("StopSession command queued for beacon {0} on listener {1}", beaconHash, listenerHash); - } - - return grpc::Status::OK; - } - } - } - - m_logger->warn("StopSession request ignored: session {0} on listener {1} not found", beaconHash, listenerHash); - - m_logger->trace("StopSession end"); - - return grpc::Status::OK; + return m_listenerSessionService->stopSession(*sessionToStop, response); } void TeamServer::socksThread() @@ -800,180 +376,13 @@ grpc::Status TeamServer::SendCmdToSession(grpc::ServerContext* context, const te auto authStatus = ensureAuthenticated(context); if (!authStatus.ok()) return authStatus; - - m_logger->trace("SendCmdToSession"); - - std::string input = command->cmd(); - std::string beaconHash = command->beaconhash(); - std::string listenerHash = command->listenerhash(); - - for (int i = 0; i < m_listeners.size(); i++) - { - if (m_listeners[i]->isSessionExist(beaconHash, listenerHash)) - { - std::shared_ptr session = m_listeners[i]->getSessionPtr(beaconHash, listenerHash); - std::string os = session->getOs(); - bool isWindows = false; - if (os == "Windows") - isWindows = true; - - m_logger->trace("SendCmdToSession: beaconHash {0} listenerHash {1}", beaconHash, listenerHash); - if (!input.empty()) - { - C2Message c2Message; - int res = prepMsg(input, c2Message, isWindows); - - m_logger->debug("SendCmdToSession {0} {1} {2}", beaconHash, c2Message.instruction(), c2Message.cmd()); - - // echec init message - if (res != 0) - { - std::string hint = c2Message.returnvalue(); - - response->set_message(hint); - response->set_status(teamserverapi::KO); - - m_logger->debug("SendCmdToSession Fail prepMsg {0}", hint); - } - - // Set the uuid to track the message and correlate with the response to get a clean output in the client - if (!c2Message.instruction().empty()) - { - m_logger->info("Queued command for beacon {} → '{}'", beaconHash.substr(0, 8), input); - - std::string inputFile = c2Message.inputfile(); - const std::string& payload = c2Message.data(); - - if (!inputFile.empty() && !payload.empty()) - { - std::string md5 = computeBufferMd5(payload); - m_logger->info( - "File attached to task: '{}' | size={} bytes | MD5={}", - inputFile, - payload.size(), - md5); - } - - std::string uuid = generateUUID8(); - c2Message.set_uuid(uuid); - m_listeners[i]->queueTask(beaconHash, c2Message); - - c2Message.set_cmd(input); - c2Message.set_data(""); - m_sentC2Messages.push_back(std::move(c2Message)); - } - } - } - } - - m_logger->trace("SendCmdToSession end"); - - return grpc::Status::OK; + return m_listenerSessionService->sendCmdToSession(*command, response); } int TeamServer::handleCmdResponse() { - m_logger->trace("handleCmdResponse"); - while (m_handleCmdResponseThreadRuning) - { - // loop through all the listeners - for (int i = 0; i < m_listeners.size(); i++) - { - // loop through all the sessions - int nbSession = m_listeners[i]->getNumberOfSession(); - for (int kk = 0; kk < nbSession; kk++) - { - std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - std::string beaconHash = session->getBeaconHash(); - - // loop through all the messages - C2Message c2Message = m_listeners[i]->getTaskResult(beaconHash); - while (!c2Message.instruction().empty()) - { - m_logger->trace("GetResponseFromSession {0} {1} {2}", beaconHash, c2Message.instruction(), c2Message.cmd()); - - std::string instructionCmd = c2Message.instruction(); - std::string errorMsg; - - // check if the message is just a listener polling mean to update listener informations - if (instructionCmd == ListenerPollCmd) - { - m_logger->debug("beaconHash {0} {1}", beaconHash, c2Message.returnvalue()); - - // Do nothing and continue with the next item - c2Message = m_listeners[i]->getTaskResult(beaconHash); - continue; - } - - // check if the message is from a loaded module and handle: - // - followup - // - the resoltion of the error code - for (auto it = m_moduleCmd.begin(); it != m_moduleCmd.end(); ++it) - { - // djb2 produce a unsigne long long - if (instructionCmd == (*it)->getName() || instructionCmd == std::to_string((*it)->getHash())) - { - m_logger->debug("Call followUp"); - (*it)->followUp(c2Message); - (*it)->errorCodeToMsg(c2Message, errorMsg); - } - } - - // check if the message is from a common module and handle: - // - the resoltion of the error code - std::string ccInstructionString = m_commonCommands.translateCmdToInstruction(instructionCmd); - for (int i = 0; i < m_commonCommands.getNumberOfCommand(); i++) - if (ccInstructionString == m_commonCommands.getCommand(i)) - m_commonCommands.errorCodeToMsg(c2Message, errorMsg); - - m_logger->debug("GetResponseFromSession {0} {1}", beaconHash, c2Message.uuid()); - - // Get the command lign sent from the list of sent messages using the uuid to get a clean client output - std::string cmd = c2Message.cmd(); - for (int jj = 0; jj < m_sentC2Messages.size(); jj++) - { - if (m_sentC2Messages[jj].uuid() == c2Message.uuid()) - { - cmd = m_sentC2Messages[jj].cmd(); - m_sentC2Messages.erase(m_sentC2Messages.begin() + jj); - break; - } - } - - m_logger->debug("GetResponseFromSession {0} {1}", beaconHash, cmd); - - // Send the response to the client - teamserverapi::CommandResponse commandResponseTmp; - commandResponseTmp.set_beaconhash(beaconHash); - commandResponseTmp.set_instruction(cmd); - commandResponseTmp.set_cmd(""); - if (!errorMsg.empty()) - { - commandResponseTmp.set_response(errorMsg); - m_cmdResponses.push_back(commandResponseTmp); - } - else if (!c2Message.returnvalue().empty()) - { - commandResponseTmp.set_response(c2Message.returnvalue()); - m_cmdResponses.push_back(commandResponseTmp); - } - else - { - m_logger->debug("GetResponseFromSession no output"); - } - - // Get the next message - c2Message = m_listeners[i]->getTaskResult(beaconHash); - } - } - } - - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - } - - m_logger->trace("handleCmdResponse end"); - + m_listenerSessionService->handleCmdResponse(); return 0; } @@ -982,52 +391,11 @@ grpc::Status TeamServer::GetResponseFromSession(grpc::ServerContext* context, co auto authStatus = ensureAuthenticated(context); if (!authStatus.ok()) return authStatus; - - m_logger->trace("GetResponseFromSession"); - - std::string targetSession = session->beaconhash(); - - // Retrieve all client metadata - auto client_metadata = context->client_metadata(); - - std::string clientId; - // Iterate through metadata and print key-value pairs - for (const auto& meta : client_metadata) - { - std::string key(meta.first.data(), meta.first.length()); - std::string value(meta.second.data(), meta.second.length()); - // std::cout << "Metadata - Key: " << key << ", Value: " << value << std::endl; - - if (key == "clientid") - clientId = value; - } - - if (clientId.empty()) - return grpc::Status::OK; - - // New client detected - Initialize entry for this client - if (m_sentResponses.find(clientId) == m_sentResponses.end()) - m_sentResponses[clientId] = {}; - - std::vector& sentIndices = m_sentResponses[clientId]; - for (size_t i = 0; i < m_cmdResponses.size(); ++i) - { - if (targetSession == m_cmdResponses[i].beaconhash()) - { - // If the response was not already sent to this client - if (std::find(sentIndices.begin(), sentIndices.end(), i) == sentIndices.end()) - { - writer->Write(m_cmdResponses[i]); - sentIndices.push_back(i); - } - } - } - - return grpc::Status::OK; - - m_logger->trace("GetResponseFromSession end"); - - return grpc::Status::OK; + return m_listenerSessionService->streamResponsesForSession( + session->beaconhash(), + context->client_metadata(), + [&](const teamserverapi::CommandResponse& commandResponse) + { return writer->Write(commandResponse); }); } const std::string HelpCmd = "help"; diff --git a/teamServer/teamServer/TeamServer.hpp b/teamServer/teamServer/TeamServer.hpp index fed81a4..08bf0f3 100644 --- a/teamServer/teamServer/TeamServer.hpp +++ b/teamServer/teamServer/TeamServer.hpp @@ -29,6 +29,7 @@ #include "nlohmann/json.hpp" class TeamServerAuthManager; +class TeamServerListenerSessionService; class TeamServer final : public teamserverapi::TeamServerApi::Service { @@ -96,4 +97,5 @@ class TeamServer final : public teamserverapi::TeamServerApi::Service std::vector m_sentC2Messages; std::unique_ptr m_authManager; + std::unique_ptr m_listenerSessionService; }; diff --git a/teamServer/teamServer/TeamServerListenerSessionService.cpp b/teamServer/teamServer/TeamServerListenerSessionService.cpp new file mode 100644 index 0000000..a010c3a --- /dev/null +++ b/teamServer/teamServer/TeamServerListenerSessionService.cpp @@ -0,0 +1,639 @@ +#include "TeamServerListenerSessionService.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "listener/ListenerDns.hpp" +#include "listener/ListenerGithub.hpp" +#include "listener/ListenerHttp.hpp" +#include "listener/ListenerTcp.hpp" + +namespace +{ +std::string generateUUID8() +{ + const char charset[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + const size_t length = 8; + std::random_device rd; + std::mt19937 generator(rd()); + std::uniform_int_distribution<> distribution(0, sizeof(charset) - 2); + + std::string uuid; + for (size_t i = 0; i < length; ++i) + { + uuid += charset[distribution(generator)]; + } + return uuid; +} + +std::string computeBufferMd5(const std::string& buffer) +{ + if (buffer.empty()) + return ""; + + unsigned char result[MD5_DIGEST_LENGTH]; + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, buffer.data(), buffer.size()); + MD5_Final(result, &ctx); + + std::ostringstream oss; + for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(result[i]); + + return oss.str(); +} + +std::string extractClientId(const std::multimap& metadata) +{ + for (const auto& meta : metadata) + { + if (std::string(meta.first.data(), meta.first.length()) == "clientid") + return std::string(meta.second.data(), meta.second.length()); + } + return ""; +} +} // namespace + +TeamServerListenerSessionService::TeamServerListenerSessionService( + std::shared_ptr logger, + const nlohmann::json& config, + std::vector>& listeners, + std::vector>& moduleCmd, + CommonCommands& commonCommands, + std::vector& cmdResponses, + std::unordered_map>& sentResponses, + std::vector& sentC2Messages, + PrepMsgCallback prepMsg) + : m_logger(std::move(logger)), + m_config(config), + m_listeners(listeners), + m_moduleCmd(moduleCmd), + m_commonCommands(commonCommands), + m_cmdResponses(cmdResponses), + m_sentResponses(sentResponses), + m_sentC2Messages(sentC2Messages), + m_prepMsg(std::move(prepMsg)) +{ +} + +grpc::Status TeamServerListenerSessionService::streamListeners(const TeamServerListenerSessionService::ListenerEmitter& emit) +{ + m_logger->trace("GetListeners"); + + for (size_t i = 0; i < m_listeners.size(); i++) + { + teamserverapi::Listener listener; + listener.set_listenerhash(m_listeners[i]->getListenerHash()); + + std::string type = m_listeners[i]->getType(); + listener.set_type(type); + if (type == ListenerHttpType || type == ListenerHttpsType || type == ListenerTcpType) + { + listener.set_ip(m_listeners[i]->getParam1()); + listener.set_port(std::stoi(m_listeners[i]->getParam2())); + } + else if (type == ListenerSmbType) + { + listener.set_ip(m_listeners[i]->getParam1()); + listener.set_domain(m_listeners[i]->getParam2()); + } + else if (type == ListenerGithubType) + { + listener.set_project(m_listeners[i]->getParam1()); + listener.set_token(m_listeners[i]->getParam2()); + } + else if (type == ListenerDnsType) + { + listener.set_domain(m_listeners[i]->getParam1()); + listener.set_port(std::stoi(m_listeners[i]->getParam2())); + } + listener.set_numberofsession(m_listeners[i]->getNumberOfSession()); + + if (!emit(listener)) + return grpc::Status::OK; + + int nbSession = static_cast(m_listeners[i]->getNumberOfSession()); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); + if (!session || session->isSessionKilled()) + continue; + + for (auto it = session->getListener().begin(); it != session->getListener().end(); ++it) + { + m_logger->trace("|-> sessionListenerList {0} {1} {2}", it->getType(), it->getParam1(), it->getParam2()); + + teamserverapi::Listener childListener; + childListener.set_listenerhash(it->getListenerHash()); + childListener.set_beaconhash(session->getBeaconHash()); + std::string childType = it->getType(); + childListener.set_type(childType); + if (childType == ListenerTcpType) + { + childListener.set_ip(it->getParam1()); + childListener.set_port(std::stoi(it->getParam2())); + } + else if (childType == ListenerSmbType) + { + childListener.set_ip(it->getParam1()); + childListener.set_domain(it->getParam2()); + } + + if (!emit(childListener)) + return grpc::Status::OK; + } + } + } + + m_logger->trace("GetListeners end"); + return grpc::Status::OK; +} + +grpc::Status TeamServerListenerSessionService::addListener(const teamserverapi::Listener& listenerToCreate) +{ + m_logger->trace("AddListener"); + const std::string type = listenerToCreate.type(); + + if (type == ListenerGithubType) + { + auto object = std::find_if( + m_listeners.begin(), + m_listeners.end(), + [&](std::shared_ptr& obj) + { + return obj->getType() == listenerToCreate.type() && + obj->getParam1() == listenerToCreate.project() && + obj->getParam2() == listenerToCreate.token(); + }); + + if (object != m_listeners.end()) + { + m_logger->warn("Add listener failed: Listener already exist"); + return grpc::Status::OK; + } + } + else if (type == ListenerDnsType) + { + auto existingDns = std::find_if( + m_listeners.begin(), + m_listeners.end(), + [&](std::shared_ptr& obj) + { + return obj->getType() == ListenerDnsType && + obj->getParam1() == listenerToCreate.domain() && + obj->getParam2() == std::to_string(listenerToCreate.port()); + }); + + if (existingDns != m_listeners.end()) + { + m_logger->warn("Add listener failed: DNS listener already running on {0}:{1}", + listenerToCreate.domain(), + std::to_string(listenerToCreate.port())); + return grpc::Status::OK; + } + } + else + { + auto object = std::find_if( + m_listeners.begin(), + m_listeners.end(), + [&](std::shared_ptr& obj) + { + return obj->getType() == listenerToCreate.type() && + obj->getParam1() == listenerToCreate.ip() && + obj->getParam2() == std::to_string(listenerToCreate.port()); + }); + + if (object != m_listeners.end()) + { + m_logger->warn("Add listener failed: Listener already exist"); + return grpc::Status::OK; + } + } + + if (type == ListenerTcpType) + { + std::shared_ptr listenerTcp = std::make_shared(listenerToCreate.ip(), listenerToCreate.port(), m_config); + if (listenerTcp->init() > 0) + { + listenerTcp->setIsPrimary(); + m_listeners.push_back(std::move(listenerTcp)); + m_logger->info("AddListener Tcp {0}:{1}", listenerToCreate.ip(), std::to_string(listenerToCreate.port())); + } + else + { + m_logger->error("Error: AddListener Tcp {0}:{1}", listenerToCreate.ip(), std::to_string(listenerToCreate.port())); + } + } + else if (type == ListenerHttpType) + { + std::shared_ptr listenerHttp = std::make_shared(listenerToCreate.ip(), listenerToCreate.port(), m_config, false); + if (listenerHttp->init() > 0) + { + listenerHttp->setIsPrimary(); + m_listeners.push_back(std::move(listenerHttp)); + m_logger->info("AddListener Http {0}:{1}", listenerToCreate.ip(), std::to_string(listenerToCreate.port())); + } + else + { + m_logger->error("Error: AddListener Http {0}:{1}", listenerToCreate.ip(), std::to_string(listenerToCreate.port())); + } + } + else if (type == ListenerHttpsType) + { + std::shared_ptr listenerHttps = std::make_shared(listenerToCreate.ip(), listenerToCreate.port(), m_config, true); + if (listenerHttps->init() > 0) + { + listenerHttps->setIsPrimary(); + m_listeners.push_back(std::move(listenerHttps)); + m_logger->info("AddListener Https {0}:{1}", listenerToCreate.ip(), std::to_string(listenerToCreate.port())); + } + else + { + m_logger->error("Error: AddListener Https {0}:{1}", listenerToCreate.ip(), std::to_string(listenerToCreate.port())); + } + } + else if (type == ListenerGithubType) + { + std::shared_ptr listenerGithub = std::make_shared(listenerToCreate.project(), listenerToCreate.token(), m_config); + listenerGithub->setIsPrimary(); + m_listeners.push_back(std::move(listenerGithub)); + m_logger->info("AddListener Github {0}:{1}", listenerToCreate.project(), listenerToCreate.token()); + } + else if (type == ListenerDnsType) + { + std::shared_ptr listenerDns = std::make_shared(listenerToCreate.domain(), listenerToCreate.port(), m_config); + listenerDns->setIsPrimary(); + m_listeners.push_back(std::move(listenerDns)); + m_logger->info("AddListener Dns {0}:{1}", listenerToCreate.domain(), std::to_string(listenerToCreate.port())); + } + + m_logger->trace("AddListener end"); + return grpc::Status::OK; +} + +grpc::Status TeamServerListenerSessionService::stopListener(const teamserverapi::Listener& listenerToStop, teamserverapi::Response* response) +{ + (void)response; + m_logger->trace("StopListener"); + + const std::string listenerHash = listenerToStop.listenerhash(); + bool removedPrimary = false; + bool stopCommandSent = false; + + auto object = std::find_if( + m_listeners.begin(), + m_listeners.end(), + [&](std::shared_ptr& obj) + { return obj->getListenerHash() == listenerHash; }); + + if (object != m_listeners.end()) + { + m_listeners.erase(std::remove(m_listeners.begin(), m_listeners.end(), *object), m_listeners.end()); + removedPrimary = true; + } + + for (size_t i = 0; i < m_listeners.size(); i++) + { + int nbSession = static_cast(m_listeners[i]->getNumberOfSession()); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); + if (!session) + continue; + + std::vector sessionListener = session->getListener(); + for (size_t j = 0; j < sessionListener.size(); j++) + { + if (listenerHash == sessionListener[j].getListenerHash()) + { + std::string input = "listener stop " + sessionListener[j].getListenerHash(); + std::string beaconHash = session->getBeaconHash(); + + C2Message c2Message; + int res = m_prepMsg(input, c2Message, true); + if (res != 0) + { + std::string hint = c2Message.returnvalue(); + response->set_message(hint); + response->set_status(teamserverapi::KO); + } + + if (!c2Message.instruction().empty()) + { + std::string uuid = generateUUID8(); + c2Message.set_uuid(uuid); + m_listeners[i]->queueTask(beaconHash, c2Message); + + c2Message.set_cmd(input); + c2Message.set_data(""); + m_sentC2Messages.push_back(std::move(c2Message)); + stopCommandSent = true; + } + } + } + } + } + + if (removedPrimary || stopCommandSent) + m_logger->info("StopListener completed for {0} (primary removed: {1}, stop commands sent: {2})", + listenerHash, + removedPrimary ? "yes" : "no", + stopCommandSent ? "yes" : "no"); + else + m_logger->warn("StopListener request ignored: listener {0} not found", listenerHash); + + m_logger->trace("StopListener end"); + return grpc::Status::OK; +} + +bool TeamServerListenerSessionService::isListenerAlive(const std::string& listenerHash) const +{ + m_logger->trace("isListenerAlive"); + + for (size_t i = 0; i < m_listeners.size(); i++) + { + if (m_listeners[i]->getListenerHash() == listenerHash) + return true; + + int nbSession = static_cast(m_listeners[i]->getNumberOfSession()); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); + if (!session || session->isSessionKilled()) + continue; + + std::vector sessionListenerList(session->getListener().begin(), session->getListener().end()); + for (size_t j = 0; j < sessionListenerList.size(); j++) + { + if (sessionListenerList[j].getListenerHash() == listenerHash) + return true; + } + } + } + + m_logger->trace("isListenerAlive end"); + return false; +} + +grpc::Status TeamServerListenerSessionService::streamSessions(const TeamServerListenerSessionService::SessionEmitter& emit) +{ + m_logger->trace("GetSessions"); + + for (size_t i = 0; i < m_listeners.size(); i++) + { + m_logger->trace("Listener {0}", m_listeners[i]->getListenerHash()); + + int nbSession = static_cast(m_listeners[i]->getNumberOfSession()); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); + if (!session) + continue; + + teamserverapi::Session sessionTmp; + sessionTmp.set_listenerhash(session->getListenerHash()); + sessionTmp.set_beaconhash(session->getBeaconHash()); + sessionTmp.set_hostname(session->getHostname()); + sessionTmp.set_username(session->getUsername()); + sessionTmp.set_arch(session->getArch()); + sessionTmp.set_privilege(session->getPrivilege()); + sessionTmp.set_os(session->getOs()); + sessionTmp.set_lastproofoflife(session->getLastProofOfLife()); + sessionTmp.set_killed(session->isSessionKilled()); + sessionTmp.set_internalips(session->getInternalIps()); + sessionTmp.set_processid(session->getProcessId()); + sessionTmp.set_additionalinformation(session->getAdditionalInformation()); + + if (!session->isSessionKilled() && isListenerAlive(session->getListenerHash())) + { + if (!emit(sessionTmp)) + return grpc::Status::OK; + } + } + } + + m_logger->trace("GetSessions end"); + return grpc::Status::OK; +} + +grpc::Status TeamServerListenerSessionService::stopSession(const teamserverapi::Session& sessionToStop, teamserverapi::Response* response) +{ + m_logger->trace("StopSession"); + + const std::string beaconHash = sessionToStop.beaconhash(); + const std::string listenerHash = sessionToStop.listenerhash(); + + if (beaconHash.size() == SizeBeaconHash) + { + for (size_t i = 0; i < m_listeners.size(); i++) + { + if (m_listeners[i]->isSessionExist(beaconHash, listenerHash)) + { + C2Message c2Message; + int res = m_prepMsg(EndInstruction, c2Message, true); + + if (res != 0) + { + std::string hint = c2Message.returnvalue(); + response->set_message(hint); + response->set_status(teamserverapi::KO); + } + + if (!c2Message.instruction().empty()) + { + m_listeners[i]->queueTask(beaconHash, c2Message); + m_listeners[i]->markSessionKilled(beaconHash); + m_logger->info("StopSession command queued for beacon {0} on listener {1}", beaconHash, listenerHash); + } + + m_logger->trace("StopSession end"); + return grpc::Status::OK; + } + } + } + + m_logger->warn("StopSession request ignored: session {0} on listener {1} not found", beaconHash, listenerHash); + m_logger->trace("StopSession end"); + return grpc::Status::OK; +} + +grpc::Status TeamServerListenerSessionService::sendCmdToSession(const teamserverapi::Command& command, teamserverapi::Response* response) +{ + m_logger->trace("SendCmdToSession"); + + const std::string input = command.cmd(); + const std::string beaconHash = command.beaconhash(); + const std::string listenerHash = command.listenerhash(); + + for (size_t i = 0; i < m_listeners.size(); i++) + { + if (!m_listeners[i]->isSessionExist(beaconHash, listenerHash)) + continue; + + std::shared_ptr session = m_listeners[i]->getSessionPtr(beaconHash, listenerHash); + bool isWindows = session && session->getOs() == "Windows"; + + if (!input.empty()) + { + C2Message c2Message; + int res = m_prepMsg(input, c2Message, isWindows); + + m_logger->debug("SendCmdToSession {0} {1} {2}", beaconHash, c2Message.instruction(), c2Message.cmd()); + + if (res != 0) + { + std::string hint = c2Message.returnvalue(); + response->set_message(hint); + response->set_status(teamserverapi::KO); + m_logger->debug("SendCmdToSession Fail prepMsg {0}", hint); + } + + if (!c2Message.instruction().empty()) + { + m_logger->info("Queued command for beacon {} -> '{}'", beaconHash.substr(0, 8), input); + + const std::string& inputFile = c2Message.inputfile(); + const std::string& payload = c2Message.data(); + if (!inputFile.empty() && !payload.empty()) + { + std::string md5 = computeBufferMd5(payload); + m_logger->info("File attached to task: '{}' | size={} bytes | MD5={}", inputFile, payload.size(), md5); + } + + std::string uuid = generateUUID8(); + c2Message.set_uuid(uuid); + m_listeners[i]->queueTask(beaconHash, c2Message); + + c2Message.set_cmd(input); + c2Message.set_data(""); + m_sentC2Messages.push_back(std::move(c2Message)); + } + } + } + + m_logger->trace("SendCmdToSession end"); + return grpc::Status::OK; +} + +int TeamServerListenerSessionService::handleCmdResponse() +{ + m_logger->trace("handleCmdResponse"); + + for (size_t i = 0; i < m_listeners.size(); i++) + { + int nbSession = static_cast(m_listeners[i]->getNumberOfSession()); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); + if (!session) + continue; + + std::string beaconHash = session->getBeaconHash(); + C2Message c2Message = m_listeners[i]->getTaskResult(beaconHash); + while (!c2Message.instruction().empty()) + { + m_logger->trace("GetResponseFromSession {0} {1} {2}", beaconHash, c2Message.instruction(), c2Message.cmd()); + + std::string instructionCmd = c2Message.instruction(); + std::string errorMsg; + + if (instructionCmd == ListenerPollCmd) + { + m_logger->debug("beaconHash {0} {1}", beaconHash, c2Message.returnvalue()); + c2Message = m_listeners[i]->getTaskResult(beaconHash); + continue; + } + + for (auto it = m_moduleCmd.begin(); it != m_moduleCmd.end(); ++it) + { + if (instructionCmd == (*it)->getName() || instructionCmd == std::to_string((*it)->getHash())) + { + (*it)->followUp(c2Message); + (*it)->errorCodeToMsg(c2Message, errorMsg); + } + } + + std::string ccInstructionString = m_commonCommands.translateCmdToInstruction(instructionCmd); + for (int ii = 0; ii < m_commonCommands.getNumberOfCommand(); ii++) + { + if (ccInstructionString == m_commonCommands.getCommand(ii)) + m_commonCommands.errorCodeToMsg(c2Message, errorMsg); + } + + std::string cmd = c2Message.cmd(); + for (size_t jj = 0; jj < m_sentC2Messages.size(); jj++) + { + if (m_sentC2Messages[jj].uuid() == c2Message.uuid()) + { + cmd = m_sentC2Messages[jj].cmd(); + m_sentC2Messages.erase(m_sentC2Messages.begin() + static_cast(jj)); + break; + } + } + + teamserverapi::CommandResponse commandResponseTmp; + commandResponseTmp.set_beaconhash(beaconHash); + commandResponseTmp.set_instruction(cmd); + commandResponseTmp.set_cmd(""); + if (!errorMsg.empty()) + { + commandResponseTmp.set_response(errorMsg); + m_cmdResponses.push_back(commandResponseTmp); + } + else if (!c2Message.returnvalue().empty()) + { + commandResponseTmp.set_response(c2Message.returnvalue()); + m_cmdResponses.push_back(commandResponseTmp); + } + else + { + m_logger->debug("GetResponseFromSession no output"); + } + + c2Message = m_listeners[i]->getTaskResult(beaconHash); + } + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + return 0; +} + +grpc::Status TeamServerListenerSessionService::streamResponsesForSession( + const std::string& targetSession, + const std::multimap& metadata, + const TeamServerListenerSessionService::CommandResponseEmitter& emit) +{ + m_logger->trace("GetResponseFromSession"); + + const std::string clientId = extractClientId(metadata); + if (clientId.empty()) + return grpc::Status::OK; + + if (m_sentResponses.find(clientId) == m_sentResponses.end()) + m_sentResponses[clientId] = {}; + + std::vector& sentIndices = m_sentResponses[clientId]; + for (size_t i = 0; i < m_cmdResponses.size(); ++i) + { + if (targetSession == m_cmdResponses[i].beaconhash()) + { + if (std::find(sentIndices.begin(), sentIndices.end(), static_cast(i)) == sentIndices.end()) + { + if (!emit(m_cmdResponses[i])) + return grpc::Status::OK; + sentIndices.push_back(static_cast(i)); + } + } + } + + return grpc::Status::OK; +} diff --git a/teamServer/teamServer/TeamServerListenerSessionService.hpp b/teamServer/teamServer/TeamServerListenerSessionService.hpp new file mode 100644 index 0000000..149e21a --- /dev/null +++ b/teamServer/teamServer/TeamServerListenerSessionService.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "TeamServerApi.pb.h" +#include "listener/Listener.hpp" +#include "modules/ModuleCmd/CommonCommand.hpp" +#include "modules/ModuleCmd/ModuleCmd.hpp" +#include "nlohmann/json.hpp" +#include "spdlog/logger.h" + +class TeamServerListenerSessionService +{ +public: + using PrepMsgCallback = std::function; + using ListenerEmitter = std::function; + using SessionEmitter = std::function; + using CommandResponseEmitter = std::function; + + TeamServerListenerSessionService( + std::shared_ptr logger, + const nlohmann::json& config, + std::vector>& listeners, + std::vector>& moduleCmd, + CommonCommands& commonCommands, + std::vector& cmdResponses, + std::unordered_map>& sentResponses, + std::vector& sentC2Messages, + PrepMsgCallback prepMsg); + + grpc::Status streamListeners(const ListenerEmitter& emit); + grpc::Status addListener(const teamserverapi::Listener& listenerToCreate); + grpc::Status stopListener(const teamserverapi::Listener& listenerToStop, teamserverapi::Response* response); + + grpc::Status streamSessions(const SessionEmitter& emit); + grpc::Status stopSession(const teamserverapi::Session& sessionToStop, teamserverapi::Response* response); + grpc::Status sendCmdToSession(const teamserverapi::Command& command, teamserverapi::Response* response); + grpc::Status streamResponsesForSession( + const std::string& targetSession, + const std::multimap& metadata, + const CommandResponseEmitter& emit); + + int handleCmdResponse(); + bool isListenerAlive(const std::string& listenerHash) const; + +private: + std::shared_ptr m_logger; + const nlohmann::json& m_config; + std::vector>& m_listeners; + std::vector>& m_moduleCmd; + CommonCommands& m_commonCommands; + std::vector& m_cmdResponses; + std::unordered_map>& m_sentResponses; + std::vector& m_sentC2Messages; + PrepMsgCallback m_prepMsg; +}; diff --git a/teamServer/tests/TeamServerListenerSessionServiceTests.cpp b/teamServer/tests/TeamServerListenerSessionServiceTests.cpp new file mode 100644 index 0000000..f674fcb --- /dev/null +++ b/teamServer/tests/TeamServerListenerSessionServiceTests.cpp @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "TeamServerListenerSessionService.hpp" + +namespace +{ +class TestListener final : public Listener +{ +public: + TestListener(const std::string& param1, const std::string& param2, const std::string& type, const std::string& hash) + : Listener(param1, param2, type) + { + m_listenerHash = hash; + } + + std::shared_ptr addSession( + const std::string& listenerHash, + const std::string& beaconHash, + const std::string& hostname, + const std::string& username, + const std::string& arch, + const std::string& privilege, + const std::string& os) + { + auto session = std::make_shared(listenerHash, beaconHash, hostname, username, arch, privilege, os); + m_sessions.push_back(session); + return session; + } +}; + +std::shared_ptr makeLogger() +{ + auto logger = std::make_shared("tests"); + logger->set_level(spdlog::level::off); + return logger; +} + +std::multimap makeMetadata(std::string& clientIdKey, std::string& clientIdValue) +{ + std::multimap metadata; + metadata.emplace( + grpc::string_ref(clientIdKey.data(), clientIdKey.size()), + grpc::string_ref(clientIdValue.data(), clientIdValue.size())); + return metadata; +} + +void testCollectListenersAndSessions() +{ + nlohmann::json config = { + {"LogLevel", "off"}, + {"HttpsListener", {{"PortBind", 0}}}, + {"HttpListener", {{"PortBind", 0}}}, + {"SmbListener", {{"Pipename", "pipe"}}}, + {"DnsListener", {{"PortBind", 0}}}}; + auto logger = makeLogger(); + + std::vector> listeners; + auto primaryListener = std::make_shared("127.0.0.1", "8443", ListenerHttpsType, "listener-primary"); + auto session = primaryListener->addSession("listener-primary", "ABCDEFGH12345678", "host", "user", "x64", "admin", "Linux"); + session->addListener("listener-child", ListenerTcpType, "10.0.0.1", "9001"); + listeners.push_back(primaryListener); + + std::vector> moduleCmd; + CommonCommands commonCommands; + std::vector cmdResponses; + std::unordered_map> sentResponses; + std::vector sentC2Messages; + + TeamServerListenerSessionService service( + logger, + config, + listeners, + moduleCmd, + commonCommands, + cmdResponses, + sentResponses, + sentC2Messages, + [](const std::string&, C2Message& c2Message, bool) + { + c2Message.set_instruction("noop"); + return 0; + }); + + std::vector streamedListeners; + assert(service.streamListeners([&](const teamserverapi::Listener& listener) + { + streamedListeners.push_back(listener); + return true; + }) + .ok()); + assert(streamedListeners.size() == 2); + assert(streamedListeners[0].listenerhash() == "listener-primary"); + assert(streamedListeners[1].listenerhash() == "listener-child"); + + std::vector streamedSessions; + assert(service.streamSessions([&](const teamserverapi::Session& sessionInfo) + { + streamedSessions.push_back(sessionInfo); + return true; + }) + .ok()); + assert(streamedSessions.size() == 1); + assert(streamedSessions[0].beaconhash() == "ABCDEFGH12345678"); +} + +void testQueueStopAndResponseDeduplication() +{ + nlohmann::json config = {{"LogLevel", "off"}}; + auto logger = makeLogger(); + + std::vector> listeners; + auto primaryListener = std::make_shared("127.0.0.1", "8443", ListenerHttpsType, "listener-primary"); + primaryListener->addSession("listener-primary", "ABCDEFGH12345678", "host", "user", "x64", "admin", "Windows"); + listeners.push_back(primaryListener); + + std::vector> moduleCmd; + CommonCommands commonCommands; + std::vector cmdResponses; + std::unordered_map> sentResponses; + std::vector sentC2Messages; + + TeamServerListenerSessionService service( + logger, + config, + listeners, + moduleCmd, + commonCommands, + cmdResponses, + sentResponses, + sentC2Messages, + [](const std::string& input, C2Message& c2Message, bool) + { + c2Message.set_instruction("instruction"); + c2Message.set_cmd(input); + c2Message.set_returnvalue(""); + return 0; + }); + + teamserverapi::Command command; + command.set_beaconhash("ABCDEFGH12345678"); + command.set_listenerhash("listener-primary"); + command.set_cmd("whoami"); + + teamserverapi::Response response; + assert(service.sendCmdToSession(command, &response).ok()); + C2Message queuedTask = primaryListener->getTask("ABCDEFGH12345678"); + assert(queuedTask.instruction() == "instruction"); + + teamserverapi::Session sessionToStop; + sessionToStop.set_beaconhash("ABCDEFGH12345678"); + sessionToStop.set_listenerhash("listener-primary"); + assert(service.stopSession(sessionToStop, &response).ok()); + C2Message stopTask = primaryListener->getTask("ABCDEFGH12345678"); + assert(stopTask.instruction() == "instruction"); + + teamserverapi::CommandResponse commandResponse; + commandResponse.set_beaconhash("ABCDEFGH12345678"); + commandResponse.set_instruction("whoami"); + commandResponse.set_response("result"); + cmdResponses.push_back(commandResponse); + + std::string clientIdKey = "clientid"; + std::string firstClientId = "client-a"; + auto firstMetadata = makeMetadata(clientIdKey, firstClientId); + + std::vector streamedResponses; + assert(service.streamResponsesForSession( + "ABCDEFGH12345678", + firstMetadata, + [&](const teamserverapi::CommandResponse& responseInfo) + { + streamedResponses.push_back(responseInfo); + return true; + }) + .ok()); + assert(streamedResponses.size() == 1); + + assert(service.streamResponsesForSession( + "ABCDEFGH12345678", + firstMetadata, + [&](const teamserverapi::CommandResponse&) + { + return false; + }) + .ok()); + + std::string secondClientId = "client-b"; + auto secondMetadata = makeMetadata(clientIdKey, secondClientId); + std::vector secondClientResponses; + assert(service.streamResponsesForSession( + "ABCDEFGH12345678", + secondMetadata, + [&](const teamserverapi::CommandResponse& responseInfo) + { + secondClientResponses.push_back(responseInfo); + return true; + }) + .ok()); + assert(secondClientResponses.size() == 1); +} +} // namespace + +int main() +{ + testCollectListenersAndSessions(); + testQueueStopAndResponseDeduplication(); + return 0; +} From b080cbbb91e850785d05cdbe4cedda8823a2d8b4 Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 15:32:04 +0200 Subject: [PATCH 11/28] Extract --- teamServer/CMakeLists.txt | 24 ++++ teamServer/teamServer/TeamServer.cpp | 115 +-------------- teamServer/teamServer/TeamServer.hpp | 2 + .../teamServer/TeamServerHelpService.cpp | 136 ++++++++++++++++++ .../teamServer/TeamServerHelpService.hpp | 35 +++++ .../tests/TeamServerHelpServiceTests.cpp | 128 +++++++++++++++++ 6 files changed, 332 insertions(+), 108 deletions(-) create mode 100644 teamServer/teamServer/TeamServerHelpService.cpp create mode 100644 teamServer/teamServer/TeamServerHelpService.hpp create mode 100644 teamServer/tests/TeamServerHelpServiceTests.cpp diff --git a/teamServer/CMakeLists.txt b/teamServer/CMakeLists.txt index 236d0f6..7fea5a6 100644 --- a/teamServer/CMakeLists.txt +++ b/teamServer/CMakeLists.txt @@ -5,6 +5,7 @@ include_directories(../core/modules/ModuleCmd) set(SOURCES_TEAMSERVER teamServer/TeamServer.cpp teamServer/TeamServerAuth.cpp +teamServer/TeamServerHelpService.cpp teamServer/TeamServerRuntimeConfig.cpp teamServer/TeamServerBootstrap.cpp teamServer/TeamServerListenerSessionService.cpp @@ -36,8 +37,11 @@ if(WITH_TESTS) add_executable(testsTestServer tests/testsTestServer.cpp teamServer/TeamServerAuth.cpp + teamServer/TeamServerHelpService.cpp teamServer/TeamServerRuntimeConfig.cpp teamServer/TeamServerBootstrap.cpp + ../core/listener/Listener.cpp + ../../thirdParty/base64/base64.cpp ) target_include_directories(testsTestServer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/teamServer @@ -54,6 +58,26 @@ if(WITH_TESTS) add_test(NAME testsTestServer COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") + add_executable(testsTeamServerHelpService + tests/TeamServerHelpServiceTests.cpp + teamServer/TeamServerHelpService.cpp + ../core/listener/Listener.cpp + ../../thirdParty/base64/base64.cpp + ) + target_include_directories(testsTeamServerHelpService PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/teamServer + ) + if(WIN32) + target_link_libraries(testsTeamServerHelpService GrpcMessages openssl::openssl ${OPENSSL_CRYPTO_LIBRARY} ZLIB::ZLIB grpc::grpc spdlog::spdlog) + else() + target_link_libraries(testsTeamServerHelpService GrpcMessages pthread openssl::openssl ZLIB::ZLIB grpc::grpc spdlog::spdlog httplib::httplib Crow::Crow dl rt) + endif() + + add_custom_command(TARGET testsTeamServerHelpService POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $ "${C2_TEST_BIN_OUTPUT_DIR}/$") + + add_test(NAME testsTeamServerHelpService COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") + add_executable(testsTeamServerListenerSessionService tests/TeamServerListenerSessionServiceTests.cpp teamServer/TeamServerListenerSessionService.cpp diff --git a/teamServer/teamServer/TeamServer.cpp b/teamServer/teamServer/TeamServer.cpp index 508c80b..016c7d2 100644 --- a/teamServer/teamServer/TeamServer.cpp +++ b/teamServer/teamServer/TeamServer.cpp @@ -2,6 +2,7 @@ #include "TeamServerAuth.hpp" #include "TeamServerBootstrap.hpp" +#include "TeamServerHelpService.hpp" #include "TeamServerListenerSessionService.hpp" #include "TeamServerRuntimeConfig.hpp" @@ -53,6 +54,11 @@ TeamServer::TeamServer(const nlohmann::json& config) m_authManager = std::make_unique(m_logger); m_authManager->configure(config); + m_helpService = std::make_unique( + m_logger, + m_listeners, + m_moduleCmd, + m_commonCommands); m_listenerSessionService = std::make_unique( m_logger, m_config, @@ -398,119 +404,12 @@ grpc::Status TeamServer::GetResponseFromSession(grpc::ServerContext* context, co { return writer->Write(commandResponse); }); } -const std::string HelpCmd = "help"; - grpc::Status TeamServer::GetHelp(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::CommandResponse* commandResponse) { auto authStatus = ensureAuthenticated(context); if (!authStatus.ok()) return authStatus; - - m_logger->trace("GetHelp"); - - std::string input = command->cmd(); - std::string beaconHash = command->beaconhash(); - std::string listenerHash = command->listenerhash(); - - bool isWindows = false; - for (int i = 0; i < m_listeners.size(); i++) - { - if (m_listeners[i]->isSessionExist(beaconHash, listenerHash)) - { - std::shared_ptr session = m_listeners[i]->getSessionPtr(beaconHash, listenerHash); - std::string os = session->getOs(); - if (os == "Windows") - isWindows = true; - } - } - - std::vector splitedCmd; - std::string delimiter = " "; - splitList(input, delimiter, splitedCmd); - - string instruction = splitedCmd[0]; - - std::string output; - if (instruction == HelpCmd) - { - if (splitedCmd.size() < 2) - { - output += "- Beacon Commands:\n"; - for (int i = 0; i < m_commonCommands.getNumberOfCommand(); i++) - { - output += " "; - output += m_commonCommands.getCommand(i); - output += "\n"; - } - - if (isWindows) - { - output += "\n- Modules Commands Windows:\n"; - for (auto it = m_moduleCmd.begin(); it != m_moduleCmd.end(); ++it) - { - if ((*it)->osCompatibility() & OS_WINDOWS) - { - output += " "; - output += (*it)->getName(); - output += "\n"; - } - } - } - else - { - output += "\n- Modules Commands Linux:\n"; - for (auto it = m_moduleCmd.begin(); it != m_moduleCmd.end(); ++it) - { - if ((*it)->osCompatibility() & OS_LINUX) - { - output += " "; - output += (*it)->getName(); - output += "\n"; - } - } - } - } - else - { - string instruction = splitedCmd[1]; - bool isModuleFound = false; - for (int i = 0; i < m_commonCommands.getNumberOfCommand(); i++) - { - if (instruction == m_commonCommands.getCommand(i)) - { - output += m_commonCommands.getHelp(instruction); - output += "\n"; - isModuleFound = true; - } - } - for (auto it = m_moduleCmd.begin(); it != m_moduleCmd.end(); ++it) - { - if (instruction == (*it)->getName()) - { - output += (*it)->getInfo(); - output += "\n"; - isModuleFound = true; - } - } - if (!isModuleFound) - { - output += "Module "; - output += instruction; - output += " not found."; - output += "\n"; - } - } - } - - teamserverapi::CommandResponse commandResponseTmp; - commandResponseTmp.set_cmd(input); - commandResponseTmp.set_response(output); - - *commandResponse = commandResponseTmp; - - m_logger->trace("GetHelp end"); - - return grpc::Status::OK; + return m_helpService->getHelp(*command, commandResponse); } // Split input based on spaces and single quotes diff --git a/teamServer/teamServer/TeamServer.hpp b/teamServer/teamServer/TeamServer.hpp index 08bf0f3..865bdbd 100644 --- a/teamServer/teamServer/TeamServer.hpp +++ b/teamServer/teamServer/TeamServer.hpp @@ -29,6 +29,7 @@ #include "nlohmann/json.hpp" class TeamServerAuthManager; +class TeamServerHelpService; class TeamServerListenerSessionService; class TeamServer final : public teamserverapi::TeamServerApi::Service @@ -97,5 +98,6 @@ class TeamServer final : public teamserverapi::TeamServerApi::Service std::vector m_sentC2Messages; std::unique_ptr m_authManager; + std::unique_ptr m_helpService; std::unique_ptr m_listenerSessionService; }; diff --git a/teamServer/teamServer/TeamServerHelpService.cpp b/teamServer/teamServer/TeamServerHelpService.cpp new file mode 100644 index 0000000..560c207 --- /dev/null +++ b/teamServer/teamServer/TeamServerHelpService.cpp @@ -0,0 +1,136 @@ +#include "TeamServerHelpService.hpp" + +#include +#include + +#include "modules/ModuleCmd/Common.hpp" + +namespace +{ +const std::string HelpCmd = "help"; +} + +TeamServerHelpService::TeamServerHelpService( + std::shared_ptr logger, + std::vector>& listeners, + std::vector>& moduleCmd, + CommonCommands& commonCommands) + : m_logger(std::move(logger)), + m_listeners(listeners), + m_moduleCmd(moduleCmd), + m_commonCommands(commonCommands) +{ +} + +grpc::Status TeamServerHelpService::getHelp(const teamserverapi::Command& command, teamserverapi::CommandResponse* commandResponse) const +{ + m_logger->trace("GetHelp"); + + const std::string input = command.cmd(); + const std::string beaconHash = command.beaconhash(); + const std::string listenerHash = command.listenerhash(); + + std::vector splitedCmd; + splitList(input, " ", splitedCmd); + + std::string output; + if (!splitedCmd.empty() && splitedCmd[0] == HelpCmd) + { + if (splitedCmd.size() < 2) + output = buildGeneralHelp(isWindowsSession(beaconHash, listenerHash)); + else + output = buildSpecificHelp(splitedCmd[1]); + } + + teamserverapi::CommandResponse commandResponseTmp; + commandResponseTmp.set_cmd(input); + commandResponseTmp.set_response(output); + *commandResponse = commandResponseTmp; + + m_logger->trace("GetHelp end"); + return grpc::Status::OK; +} + +bool TeamServerHelpService::isWindowsSession(const std::string& beaconHash, const std::string& listenerHash) const +{ + for (const std::shared_ptr& listener : m_listeners) + { + if (!listener->isSessionExist(beaconHash, listenerHash)) + continue; + + std::shared_ptr session = listener->getSessionPtr(beaconHash, listenerHash); + return session && session->getOs() == "Windows"; + } + + return false; +} + +std::string TeamServerHelpService::buildGeneralHelp(bool isWindows) const +{ + std::string output; + output += "- Beacon Commands:\n"; + for (int i = 0; i < m_commonCommands.getNumberOfCommand(); i++) + { + output += " "; + output += m_commonCommands.getCommand(i); + output += "\n"; + } + + if (isWindows) + output += "\n- Modules Commands Windows:\n"; + else + output += "\n- Modules Commands Linux:\n"; + + for (const std::unique_ptr& module : m_moduleCmd) + { + if (isWindows && (module->osCompatibility() & OS_WINDOWS)) + { + output += " "; + output += module->getName(); + output += "\n"; + } + else if (!isWindows && (module->osCompatibility() & OS_LINUX)) + { + output += " "; + output += module->getName(); + output += "\n"; + } + } + + return output; +} + +std::string TeamServerHelpService::buildSpecificHelp(const std::string& instruction) const +{ + std::string output; + bool isModuleFound = false; + + for (int i = 0; i < m_commonCommands.getNumberOfCommand(); i++) + { + if (instruction == m_commonCommands.getCommand(i)) + { + output += m_commonCommands.getHelp(instruction); + output += "\n"; + isModuleFound = true; + } + } + + for (const std::unique_ptr& module : m_moduleCmd) + { + if (instruction == module->getName()) + { + output += module->getInfo(); + output += "\n"; + isModuleFound = true; + } + } + + if (!isModuleFound) + { + output += "Module "; + output += instruction; + output += " not found.\n"; + } + + return output; +} diff --git a/teamServer/teamServer/TeamServerHelpService.hpp b/teamServer/teamServer/TeamServerHelpService.hpp new file mode 100644 index 0000000..f3f2d82 --- /dev/null +++ b/teamServer/teamServer/TeamServerHelpService.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +#include + +#include "TeamServerApi.pb.h" +#include "listener/Listener.hpp" +#include "modules/ModuleCmd/CommonCommand.hpp" +#include "modules/ModuleCmd/ModuleCmd.hpp" +#include "spdlog/logger.h" + +class TeamServerHelpService +{ +public: + TeamServerHelpService( + std::shared_ptr logger, + std::vector>& listeners, + std::vector>& moduleCmd, + CommonCommands& commonCommands); + + grpc::Status getHelp(const teamserverapi::Command& command, teamserverapi::CommandResponse* commandResponse) const; + +private: + bool isWindowsSession(const std::string& beaconHash, const std::string& listenerHash) const; + std::string buildGeneralHelp(bool isWindows) const; + std::string buildSpecificHelp(const std::string& instruction) const; + + std::shared_ptr m_logger; + std::vector>& m_listeners; + std::vector>& m_moduleCmd; + CommonCommands& m_commonCommands; +}; diff --git a/teamServer/tests/TeamServerHelpServiceTests.cpp b/teamServer/tests/TeamServerHelpServiceTests.cpp new file mode 100644 index 0000000..79f75a5 --- /dev/null +++ b/teamServer/tests/TeamServerHelpServiceTests.cpp @@ -0,0 +1,128 @@ +#include +#include +#include +#include + +#include "TeamServerHelpService.hpp" + +namespace +{ +class TestListener final : public Listener +{ +public: + explicit TestListener(const std::string& hash) + : Listener("127.0.0.1", "8443", ListenerHttpsType) + { + m_listenerHash = hash; + } + + std::shared_ptr addSession( + const std::string& listenerHash, + const std::string& beaconHash, + const std::string& os) + { + auto session = std::make_shared(listenerHash, beaconHash, "host", "user", "x64", "admin", os); + m_sessions.push_back(session); + return session; + } +}; + +class FakeModule final : public ModuleCmd +{ +public: + FakeModule(std::string name, std::string info, int compatibility) + : ModuleCmd(std::move(name)), + m_info(std::move(info)), + m_compatibility(compatibility) + { + } + + std::string getInfo() override + { + return m_info; + } + + int init(std::vector&, C2Message&) override + { + return 0; + } + + int process(C2Message&, C2Message&) override + { + return 0; + } + + int osCompatibility() override + { + return m_compatibility; + } + +private: + std::string m_info; + int m_compatibility; +}; + +std::shared_ptr makeLogger() +{ + auto logger = std::make_shared("help-tests"); + logger->set_level(spdlog::level::off); + return logger; +} + +void testGeneralHelpUsesSessionPlatform() +{ + auto logger = makeLogger(); + std::vector> listeners; + auto listener = std::make_shared("listener-primary"); + listener->addSession("listener-primary", "ABCDEFGH12345678", "Windows"); + listeners.push_back(listener); + + std::vector> moduleCmd; + moduleCmd.push_back(std::make_unique("winmod", "windows module info", OS_WINDOWS)); + moduleCmd.push_back(std::make_unique("linmod", "linux module info", OS_LINUX)); + + CommonCommands commonCommands; + TeamServerHelpService service(logger, listeners, moduleCmd, commonCommands); + + teamserverapi::Command command; + command.set_cmd("help"); + command.set_beaconhash("ABCDEFGH12345678"); + command.set_listenerhash("listener-primary"); + + teamserverapi::CommandResponse response; + assert(service.getHelp(command, &response).ok()); + assert(response.response().find("- Modules Commands Windows:") != std::string::npos); + assert(response.response().find("winmod") != std::string::npos); + assert(response.response().find("linmod") == std::string::npos); +} + +void testSpecificHelpResolvesModuleInfoAndMissingModule() +{ + auto logger = makeLogger(); + std::vector> listeners; + std::vector> moduleCmd; + moduleCmd.push_back(std::make_unique("winmod", "windows module info", OS_WINDOWS)); + + CommonCommands commonCommands; + TeamServerHelpService service(logger, listeners, moduleCmd, commonCommands); + + teamserverapi::Command moduleCommand; + moduleCommand.set_cmd("help winmod"); + teamserverapi::CommandResponse moduleResponse; + assert(service.getHelp(moduleCommand, &moduleResponse).ok()); + assert(moduleResponse.response().find("windows module info") != std::string::npos); + + teamserverapi::Command missingCommand; + missingCommand.set_cmd("help nope"); + teamserverapi::CommandResponse missingResponse; + assert(service.getHelp(missingCommand, &missingResponse).ok()); + assert(missingResponse.response() == "Module nope not found.\n"); +} +} // namespace + +int main() +{ + testGeneralHelpUsesSessionPlatform(); + testSpecificHelpResolvesModuleInfoAndMissingModule(); + return 0; +} From f586091fa678f8509cb8a6bb4c79272ba5bb08c3 Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 15:42:13 +0200 Subject: [PATCH 12/28] Extract --- teamServer/CMakeLists.txt | 22 + teamServer/teamServer/TeamServer.cpp | 334 +------------- teamServer/teamServer/TeamServer.hpp | 12 +- .../teamServer/TeamServerSocksService.cpp | 417 ++++++++++++++++++ .../teamServer/TeamServerSocksService.hpp | 69 +++ .../tests/TeamServerSocksServiceTests.cpp | 161 +++++++ 6 files changed, 676 insertions(+), 339 deletions(-) create mode 100644 teamServer/teamServer/TeamServerSocksService.cpp create mode 100644 teamServer/teamServer/TeamServerSocksService.hpp create mode 100644 teamServer/tests/TeamServerSocksServiceTests.cpp diff --git a/teamServer/CMakeLists.txt b/teamServer/CMakeLists.txt index 7fea5a6..e0becee 100644 --- a/teamServer/CMakeLists.txt +++ b/teamServer/CMakeLists.txt @@ -6,6 +6,7 @@ set(SOURCES_TEAMSERVER teamServer/TeamServer.cpp teamServer/TeamServerAuth.cpp teamServer/TeamServerHelpService.cpp +teamServer/TeamServerSocksService.cpp teamServer/TeamServerRuntimeConfig.cpp teamServer/TeamServerBootstrap.cpp teamServer/TeamServerListenerSessionService.cpp @@ -78,6 +79,27 @@ if(WITH_TESTS) add_test(NAME testsTeamServerHelpService COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") + add_executable(testsTeamServerSocksService + tests/TeamServerSocksServiceTests.cpp + teamServer/TeamServerSocksService.cpp + ../core/listener/Listener.cpp + ../../thirdParty/base64/base64.cpp + ) + target_include_directories(testsTeamServerSocksService PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/teamServer + ${CMAKE_SOURCE_DIR}/libs/libSocks5/src + ) + if(WIN32) + target_link_libraries(testsTeamServerSocksService GrpcMessages openssl::openssl ${OPENSSL_CRYPTO_LIBRARY} ZLIB::ZLIB grpc::grpc spdlog::spdlog SocksServer) + else() + target_link_libraries(testsTeamServerSocksService GrpcMessages pthread openssl::openssl ZLIB::ZLIB grpc::grpc spdlog::spdlog httplib::httplib Crow::Crow SocksServer dl rt) + endif() + + add_custom_command(TARGET testsTeamServerSocksService POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $ "${C2_TEST_BIN_OUTPUT_DIR}/$") + + add_test(NAME testsTeamServerSocksService COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") + add_executable(testsTeamServerListenerSessionService tests/TeamServerListenerSessionServiceTests.cpp teamServer/TeamServerListenerSessionService.cpp diff --git a/teamServer/teamServer/TeamServer.cpp b/teamServer/teamServer/TeamServer.cpp index 016c7d2..abf3b40 100644 --- a/teamServer/teamServer/TeamServer.cpp +++ b/teamServer/teamServer/TeamServer.cpp @@ -4,6 +4,7 @@ #include "TeamServerBootstrap.hpp" #include "TeamServerHelpService.hpp" #include "TeamServerListenerSessionService.hpp" +#include "TeamServerSocksService.hpp" #include "TeamServerRuntimeConfig.hpp" #include @@ -36,7 +37,7 @@ grpc::Status TeamServer::ensureAuthenticated(grpc::ServerContext* context) } TeamServer::TeamServer(const nlohmann::json& config) - : m_config(config), m_isSocksServerRunning(false), m_isSocksServerBinded(false) + : m_config(config) { m_logger = createTeamServerLogger(config); @@ -70,6 +71,7 @@ TeamServer::TeamServer(const nlohmann::json& config) m_sentC2Messages, [this](const std::string& input, C2Message& c2Message, bool isWindows) { return this->prepMsg(input, c2Message, isWindows); }); + m_socksService = std::make_unique(m_logger, m_listeners); // Modules m_logger->debug("TeamServer module directory path {0}", m_teamServerModulesDirectoryPath.c_str()); @@ -135,10 +137,7 @@ TeamServer::~TeamServer() { m_handleCmdResponseThreadRuning = false; m_handleCmdResponseThread->join(); - - m_isSocksServerBinded = false; - if (m_socksThread) - m_socksThread->join(); + m_socksService->shutdown(); } grpc::Status TeamServer::Authenticate(grpc::ServerContext* context, const teamserverapi::AuthRequest* request, teamserverapi::AuthResponse* response) @@ -202,181 +201,6 @@ grpc::Status TeamServer::StopSession(grpc::ServerContext* context, const teamser return m_listenerSessionService->stopSession(*sessionToStop, response); } -void TeamServer::socksThread() -{ - std::string dataIn; - std::string dataOut; - m_isSocksServerBinded = true; - while (m_isSocksServerBinded) - { - // if session is killed (beacon probably dead) we end the server - if (m_socksSession->isSessionKilled()) - { - m_isSocksServerBinded = false; - - for (std::size_t i = 0; i < m_socksServer->tunnelCount(); i++) - m_socksServer->resetTunnel(i); - } - - C2Message c2Message = m_socksListener->getSocksTaskResult(m_socksSession->getBeaconHash()); - - // if the beacon request stopSocks we end the server - if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == StopSocksCmd) - { - m_socksServer->stop(); - m_isSocksServerBinded = false; - - for (std::size_t i = 0; i < m_socksServer->tunnelCount(); i++) - m_socksServer->resetTunnel(i); - } - - for (std::size_t i = 0; i < m_socksServer->tunnelCount(); i++) - { - SocksTunnelServer* tunnel = m_socksServer->getTunnel(i); - - if (tunnel != nullptr) - { - int id = tunnel->getId(); - SocksState state = tunnel->getState(); - if (state == SocksState::INIT) - { - int ip = tunnel->getIpDst(); - int port = tunnel->getPort(); - - m_logger->debug("Socks5 to {}:{}", std::to_string(ip), std::to_string(port)); - - C2Message c2MessageToSend; - c2MessageToSend.set_instruction(Socks5Cmd); - c2MessageToSend.set_cmd(InitCmd); - c2MessageToSend.set_data(std::to_string(ip)); - c2MessageToSend.set_args(std::to_string(port)); - c2MessageToSend.set_pid(id); - - if (!c2MessageToSend.instruction().empty()) - m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); - - tunnel->setState(SocksState::HANDSHAKE); - } - else if (state == SocksState::HANDSHAKE) - { - m_logger->trace("Socks5 wait handshake {}", id); - - if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == InitCmd && c2Message.pid() == id) - { - m_logger->debug("Socks5 handshake received {}", id); - - if (c2Message.data() == "fail") - { - m_logger->debug("Socks5 handshake failed {}", id); - m_socksServer->resetTunnel(i); - } - else - { - m_logger->debug("Socks5 handshake succed {}", id); - tunnel->finishHandshake(); - tunnel->setState(SocksState::RUN); - - dataIn = ""; - int res = tunnel->process(dataIn, dataOut); - - if (res <= 0) - { - m_logger->debug("Socks5 stop"); - - m_socksServer->resetTunnel(i); - - C2Message c2MessageToSend; - c2MessageToSend.set_instruction(Socks5Cmd); - c2MessageToSend.set_cmd(StopCmd); - c2MessageToSend.set_pid(id); - - if (!c2MessageToSend.instruction().empty()) - m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); - } - else - { - m_logger->debug("Socks5 send data to beacon"); - - C2Message c2MessageToSend; - c2MessageToSend.set_instruction(Socks5Cmd); - c2MessageToSend.set_cmd(RunCmd); - c2MessageToSend.set_pid(id); - c2MessageToSend.set_data(dataOut); - - if (!c2MessageToSend.instruction().empty()) - m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); - } - } - } - else if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == StopCmd && c2Message.pid() == id) - { - m_socksServer->resetTunnel(i); - } - } - else if (state == SocksState::RUN) - { - m_logger->trace("Socks5 run {}", id); - - dataIn = ""; - if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == RunCmd && c2Message.pid() == id) - { - m_logger->debug("Socks5 {}: data received from beacon", id); - - dataIn = c2Message.data(); - - int res = tunnel->process(dataIn, dataOut); - - m_logger->debug("Socks5 process, res {}, dataIn {}, dataOut {}", res, dataIn.size(), dataOut.size()); - - // TODO do we stop if dataOut.size()==0 ???? - // if(res<=0 || dataOut.size()==0) - if (res <= 0) - { - m_logger->debug("Socks5 stop"); - - m_socksServer->resetTunnel(i); - - C2Message c2MessageToSend; - c2MessageToSend.set_instruction(Socks5Cmd); - c2MessageToSend.set_cmd(StopCmd); - c2MessageToSend.set_pid(id); - - if (!c2MessageToSend.instruction().empty()) - m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); - } - else - { - m_logger->debug("Socks5 send data to beacon"); - - C2Message c2MessageToSend; - c2MessageToSend.set_instruction(Socks5Cmd); - c2MessageToSend.set_cmd(RunCmd); - c2MessageToSend.set_pid(id); - c2MessageToSend.set_data(dataOut); - - if (!c2MessageToSend.instruction().empty()) - m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); - } - } - else if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == StopCmd && c2Message.pid() == id) - { - m_socksServer->resetTunnel(i); - } - } - } - } - - // Remove ended tunnels - m_socksServer->cleanTunnel(); - - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - } - - m_logger->info("End SocksServer binding"); - - return; -} - grpc::Status TeamServer::SendCmdToSession(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::Response* response) { auto authStatus = ensureAuthenticated(context); @@ -1038,155 +862,7 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser else if (instruction == SocksInstruction_) { m_logger->debug("socks {0}", cmd); - if (splitedCmd.size() >= 2) - { - std::string cmd = splitedCmd[1]; - - // Start a thread that handle all the communication with the beacon - if (cmd == "start") - { - if (m_isSocksServerRunning == true) - { - m_logger->warn("Error: Socks server is already running"); - responseTmp.set_result("Error: Socks server is already running"); - *response = responseTmp; - return grpc::Status::OK; - } - else - { - // TODO put the port in config - int port = 1080; - - bool isPortInUse = port_in_use(port); - if (!isPortInUse) - { - m_socksServer = std::make_unique(port); - - int maxAttempt = 3; - int attempts = 0; - while (!m_socksServer->isServerLaunched()) - { - m_socksServer->stop(); - m_socksServer->launch(); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - m_logger->debug("Wait for SocksServer to start on port {}", port); - attempts++; - if (attempts > maxAttempt) - { - m_logger->error("Error: Unable to start the socks server on port {} after {} attempts", port, maxAttempt); - break; - } - } - - if (m_socksServer->isServerStoped()) - { - m_logger->warn("Error: Socks server failed to start on port {}", port); - responseTmp.set_result("Error: Socks server failed to start on port " + std::to_string(port)); - *response = responseTmp; - return grpc::Status::OK; - } - - m_isSocksServerRunning = true; - - m_logger->info("Socks server successfully started on port {}", port); - responseTmp.set_result("Socks server successfully started on port " + std::to_string(port)); - *response = responseTmp; - return grpc::Status::OK; - } - else - { - m_logger->warn("Error: Socks server port already used"); - responseTmp.set_result("Error: Socks server port already used"); - *response = responseTmp; - return grpc::Status::OK; - } - } - } - else if (cmd == "stop") - { - m_isSocksServerBinded = false; - if (m_socksThread) - m_socksThread->join(); - m_socksThread.reset(nullptr); - - m_isSocksServerRunning = false; - if (m_socksServer) - m_socksServer->stop(); - m_socksServer.reset(nullptr); - - m_logger->info("Socks server stoped"); - responseTmp.set_result("Socks server stoped"); - *response = responseTmp; - return grpc::Status::OK; - } - else if (cmd == "bind") - { - if (!m_isSocksServerRunning) - { - m_logger->warn("Error: Socks server not running"); - responseTmp.set_result("Error: Socks server not running"); - *response = responseTmp; - return grpc::Status::OK; - } - if (m_isSocksServerBinded) - { - m_logger->warn("Error: Socks server already bind"); - responseTmp.set_result("Error: Socks server already bind"); - *response = responseTmp; - return grpc::Status::OK; - } - if (splitedCmd.size() == 3) - { - std::string beaconHash = splitedCmd[2]; - for (int i = 0; i < m_listeners.size(); i++) - { - int nbSession = m_listeners[i]->getNumberOfSession(); - for (int kk = 0; kk < nbSession; kk++) - { - std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - std::string hash = session->getBeaconHash(); - if (hash.find(beaconHash) != std::string::npos && !session->isSessionKilled()) - { - m_socksListener = m_listeners[i]; - m_socksSession = m_listeners[i]->getSessionPtr(kk); - - m_socksThread = std::make_unique(&TeamServer::socksThread, this); - - m_isSocksServerBinded = true; - m_logger->info("Socks server sucessfully binded"); - responseTmp.set_result("Socks server sucessfully binded\nThink about setting the sleep time of the beacon to 0.001 to force a good throughput"); - *response = responseTmp; - return grpc::Status::OK; - } - } - } - - m_logger->warn("Error: Socks server bind failed, session not found"); - responseTmp.set_result("Error: Socks server bind failed, session not found"); - *response = responseTmp; - return grpc::Status::OK; - } - } - else if (cmd == "unbind") - { - m_isSocksServerBinded = false; - if (m_socksThread) - m_socksThread->join(); - m_socksThread.reset(nullptr); - - m_logger->info("Socks server successfully unbinding"); - responseTmp.set_result("Socks server successfully unbinding"); - *response = responseTmp; - return grpc::Status::OK; - } - else - { - m_logger->warn("Error: Socks server command not found."); - responseTmp.set_result("Error: Socks server command not found."); - *response = responseTmp; - return grpc::Status::OK; - } - } + return m_socksService->handleCommand(splitedCmd, response); } // TODO add a clean www directory !!! else diff --git a/teamServer/teamServer/TeamServer.hpp b/teamServer/teamServer/TeamServer.hpp index 865bdbd..29e0806 100644 --- a/teamServer/teamServer/TeamServer.hpp +++ b/teamServer/teamServer/TeamServer.hpp @@ -31,6 +31,7 @@ class TeamServerAuthManager; class TeamServerHelpService; class TeamServerListenerSessionService; +class TeamServerSocksService; class TeamServer final : public teamserverapi::TeamServerApi::Service { @@ -80,16 +81,6 @@ class TeamServer final : public teamserverapi::TeamServerApi::Service std::string m_toolsDirectoryPath; std::string m_scriptsDirectoryPath; - // Socks - bool m_isSocksServerRunning; - bool m_isSocksServerBinded; - void socksThread(); - - std::unique_ptr m_socksServer; - std::unique_ptr m_socksThread; - std::shared_ptr m_socksListener; - std::shared_ptr m_socksSession; - bool m_handleCmdResponseThreadRuning; std::unique_ptr m_handleCmdResponseThread; std::vector m_cmdResponses; @@ -100,4 +91,5 @@ class TeamServer final : public teamserverapi::TeamServerApi::Service std::unique_ptr m_authManager; std::unique_ptr m_helpService; std::unique_ptr m_listenerSessionService; + std::unique_ptr m_socksService; }; diff --git a/teamServer/teamServer/TeamServerSocksService.cpp b/teamServer/teamServer/TeamServerSocksService.cpp new file mode 100644 index 0000000..4d5d3b4 --- /dev/null +++ b/teamServer/teamServer/TeamServerSocksService.cpp @@ -0,0 +1,417 @@ +#include "TeamServerSocksService.hpp" + +#include + +#include "modules/ModuleCmd/CommonCommand.hpp" + +namespace +{ +class SocksServerAdapter final : public ISocksServer +{ +public: + explicit SocksServerAdapter(int port) + : m_server(std::make_unique(port)) + { + } + + void launch() override + { + m_server->launch(); + } + + void stop() override + { + m_server->stop(); + } + + void cleanTunnel() override + { + m_server->cleanTunnel(); + } + + bool isServerStoped() const override + { + return m_server->isServerStoped(); + } + + bool isServerLaunched() const override + { + return m_server->isServerLaunched(); + } + + std::size_t tunnelCount() override + { + return m_server->tunnelCount(); + } + + SocksTunnelServer* getTunnel(std::size_t idx) override + { + return m_server->getTunnel(idx); + } + + void resetTunnel(std::size_t idx) override + { + m_server->resetTunnel(idx); + } + +private: + std::unique_ptr m_server; +}; + +bool defaultPortInUse(unsigned short) +{ + return false; +} +} // namespace + +TeamServerSocksService::TeamServerSocksService( + std::shared_ptr logger, + std::vector>& listeners, + PortInUseCallback portInUse, + ServerFactory serverFactory) + : m_logger(std::move(logger)), + m_listeners(listeners), + m_portInUse(portInUse ? std::move(portInUse) : PortInUseCallback(defaultPortInUse)), + m_serverFactory(serverFactory ? std::move(serverFactory) : ServerFactory()), + m_isSocksServerRunning(false), + m_isSocksServerBinded(false) +{ +} + +TeamServerSocksService::~TeamServerSocksService() +{ + shutdown(); +} + +grpc::Status TeamServerSocksService::handleCommand(const std::vector& splitedCmd, teamserverapi::TermCommand* response) +{ + if (splitedCmd.size() < 2) + return grpc::Status::OK; + + const std::string cmd = splitedCmd[1]; + if (cmd == "start") + { + if (m_isSocksServerRunning) + { + m_logger->warn("Error: Socks server is already running"); + response->set_result("Error: Socks server is already running"); + return grpc::Status::OK; + } + + const int port = 1080; + if (portInUse(static_cast(port))) + { + m_logger->warn("Error: Socks server port already used"); + response->set_result("Error: Socks server port already used"); + return grpc::Status::OK; + } + + m_socksServer = createServer(port); + int maxAttempt = 3; + int attempts = 0; + while (!m_socksServer->isServerLaunched()) + { + m_socksServer->stop(); + m_socksServer->launch(); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + m_logger->debug("Wait for SocksServer to start on port {}", port); + attempts++; + if (attempts > maxAttempt) + { + m_logger->error("Error: Unable to start the socks server on port {} after {} attempts", port, maxAttempt); + break; + } + } + + if (m_socksServer->isServerStoped()) + { + m_logger->warn("Error: Socks server failed to start on port {}", port); + response->set_result("Error: Socks server failed to start on port " + std::to_string(port)); + return grpc::Status::OK; + } + + m_isSocksServerRunning = true; + m_logger->info("Socks server successfully started on port {}", port); + response->set_result("Socks server successfully started on port " + std::to_string(port)); + return grpc::Status::OK; + } + + if (cmd == "stop") + { + stopServer(); + m_logger->info("Socks server stoped"); + response->set_result("Socks server stoped"); + return grpc::Status::OK; + } + + if (cmd == "bind") + { + if (!m_isSocksServerRunning) + { + m_logger->warn("Error: Socks server not running"); + response->set_result("Error: Socks server not running"); + return grpc::Status::OK; + } + if (m_isSocksServerBinded) + { + m_logger->warn("Error: Socks server already bind"); + response->set_result("Error: Socks server already bind"); + return grpc::Status::OK; + } + if (splitedCmd.size() == 3) + { + std::shared_ptr listener; + std::shared_ptr session = findSessionByPrefix(splitedCmd[2], listener); + if (session) + { + m_socksListener = std::move(listener); + m_socksSession = std::move(session); + m_socksThread = std::make_unique(&TeamServerSocksService::run, this); + m_isSocksServerBinded = true; + m_logger->info("Socks server sucessfully binded"); + response->set_result("Socks server sucessfully binded\nThink about setting the sleep time of the beacon to 0.001 to force a good throughput"); + return grpc::Status::OK; + } + } + + m_logger->warn("Error: Socks server bind failed, session not found"); + response->set_result("Error: Socks server bind failed, session not found"); + return grpc::Status::OK; + } + + if (cmd == "unbind") + { + unbindThread(); + m_logger->info("Socks server successfully unbinding"); + response->set_result("Socks server successfully unbinding"); + return grpc::Status::OK; + } + + m_logger->warn("Error: Socks server command not found."); + response->set_result("Error: Socks server command not found."); + return grpc::Status::OK; +} + +void TeamServerSocksService::shutdown() +{ + stopServer(); +} + +void TeamServerSocksService::run() +{ + std::string dataIn; + std::string dataOut; + m_isSocksServerBinded = true; + while (m_isSocksServerBinded) + { + if (m_socksSession->isSessionKilled()) + { + m_isSocksServerBinded = false; + for (std::size_t i = 0; i < m_socksServer->tunnelCount(); i++) + m_socksServer->resetTunnel(i); + } + + C2Message c2Message = m_socksListener->getSocksTaskResult(m_socksSession->getBeaconHash()); + if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == StopSocksCmd) + { + m_socksServer->stop(); + m_isSocksServerBinded = false; + for (std::size_t i = 0; i < m_socksServer->tunnelCount(); i++) + m_socksServer->resetTunnel(i); + } + + for (std::size_t i = 0; i < m_socksServer->tunnelCount(); i++) + { + SocksTunnelServer* tunnel = m_socksServer->getTunnel(i); + if (tunnel == nullptr) + continue; + + int id = tunnel->getId(); + SocksState state = tunnel->getState(); + if (state == SocksState::INIT) + { + int ip = tunnel->getIpDst(); + int port = tunnel->getPort(); + + m_logger->debug("Socks5 to {}:{}", std::to_string(ip), std::to_string(port)); + + C2Message c2MessageToSend; + c2MessageToSend.set_instruction(Socks5Cmd); + c2MessageToSend.set_cmd(InitCmd); + c2MessageToSend.set_data(std::to_string(ip)); + c2MessageToSend.set_args(std::to_string(port)); + c2MessageToSend.set_pid(id); + + if (!c2MessageToSend.instruction().empty()) + m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); + + tunnel->setState(SocksState::HANDSHAKE); + } + else if (state == SocksState::HANDSHAKE) + { + m_logger->trace("Socks5 wait handshake {}", id); + + if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == InitCmd && c2Message.pid() == id) + { + m_logger->debug("Socks5 handshake received {}", id); + + if (c2Message.data() == "fail") + { + m_logger->debug("Socks5 handshake failed {}", id); + m_socksServer->resetTunnel(i); + } + else + { + m_logger->debug("Socks5 handshake succed {}", id); + tunnel->finishHandshake(); + tunnel->setState(SocksState::RUN); + + dataIn = ""; + int res = tunnel->process(dataIn, dataOut); + if (res <= 0) + { + m_logger->debug("Socks5 stop"); + m_socksServer->resetTunnel(i); + + C2Message c2MessageToSend; + c2MessageToSend.set_instruction(Socks5Cmd); + c2MessageToSend.set_cmd(StopCmd); + c2MessageToSend.set_pid(id); + if (!c2MessageToSend.instruction().empty()) + m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); + } + else + { + m_logger->debug("Socks5 send data to beacon"); + + C2Message c2MessageToSend; + c2MessageToSend.set_instruction(Socks5Cmd); + c2MessageToSend.set_cmd(RunCmd); + c2MessageToSend.set_pid(id); + c2MessageToSend.set_data(dataOut); + if (!c2MessageToSend.instruction().empty()) + m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); + } + } + } + else if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == StopCmd && c2Message.pid() == id) + { + m_socksServer->resetTunnel(i); + } + } + else if (state == SocksState::RUN) + { + m_logger->trace("Socks5 run {}", id); + + dataIn = ""; + if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == RunCmd && c2Message.pid() == id) + { + m_logger->debug("Socks5 {}: data received from beacon", id); + + dataIn = c2Message.data(); + int res = tunnel->process(dataIn, dataOut); + + m_logger->debug("Socks5 process, res {}, dataIn {}, dataOut {}", res, dataIn.size(), dataOut.size()); + if (res <= 0) + { + m_logger->debug("Socks5 stop"); + m_socksServer->resetTunnel(i); + + C2Message c2MessageToSend; + c2MessageToSend.set_instruction(Socks5Cmd); + c2MessageToSend.set_cmd(StopCmd); + c2MessageToSend.set_pid(id); + if (!c2MessageToSend.instruction().empty()) + m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); + } + else + { + m_logger->debug("Socks5 send data to beacon"); + + C2Message c2MessageToSend; + c2MessageToSend.set_instruction(Socks5Cmd); + c2MessageToSend.set_cmd(RunCmd); + c2MessageToSend.set_pid(id); + c2MessageToSend.set_data(dataOut); + if (!c2MessageToSend.instruction().empty()) + m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); + } + } + else if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == StopCmd && c2Message.pid() == id) + { + m_socksServer->resetTunnel(i); + } + } + } + + m_socksServer->cleanTunnel(); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + m_logger->info("End SocksServer binding"); +} + +bool TeamServerSocksService::isRunning() const +{ + return m_isSocksServerRunning; +} + +bool TeamServerSocksService::isBound() const +{ + return m_isSocksServerBinded; +} + +std::shared_ptr TeamServerSocksService::findSessionByPrefix(const std::string& beaconHashPrefix, std::shared_ptr& listener) const +{ + for (const std::shared_ptr& currentListener : m_listeners) + { + int nbSession = static_cast(currentListener->getNumberOfSession()); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = currentListener->getSessionPtr(kk); + if (!session) + continue; + + std::string hash = session->getBeaconHash(); + if (hash.find(beaconHashPrefix) != std::string::npos && !session->isSessionKilled()) + { + listener = currentListener; + return session; + } + } + } + + listener.reset(); + return nullptr; +} + +std::unique_ptr TeamServerSocksService::createServer(int port) +{ + if (m_serverFactory) + return m_serverFactory(port); + return std::make_unique(port); +} + +bool TeamServerSocksService::portInUse(unsigned short port) const +{ + return m_portInUse(port); +} + +void TeamServerSocksService::unbindThread() +{ + m_isSocksServerBinded = false; + if (m_socksThread) + m_socksThread->join(); + m_socksThread.reset(); + m_socksListener.reset(); + m_socksSession.reset(); +} + +void TeamServerSocksService::stopServer() +{ + unbindThread(); + m_isSocksServerRunning = false; + if (m_socksServer) + m_socksServer->stop(); + m_socksServer.reset(); +} diff --git a/teamServer/teamServer/TeamServerSocksService.hpp b/teamServer/teamServer/TeamServerSocksService.hpp new file mode 100644 index 0000000..0da0b5b --- /dev/null +++ b/teamServer/teamServer/TeamServerSocksService.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "TeamServerApi.pb.h" +#include "SocksServer.hpp" +#include "listener/Listener.hpp" +#include "spdlog/logger.h" + +class ISocksServer +{ +public: + virtual ~ISocksServer() = default; + virtual void launch() = 0; + virtual void stop() = 0; + virtual void cleanTunnel() = 0; + virtual bool isServerStoped() const = 0; + virtual bool isServerLaunched() const = 0; + virtual std::size_t tunnelCount() = 0; + virtual SocksTunnelServer* getTunnel(std::size_t idx) = 0; + virtual void resetTunnel(std::size_t idx) = 0; +}; + +class TeamServerSocksService +{ +public: + using PortInUseCallback = std::function; + using ServerFactory = std::function(int)>; + + TeamServerSocksService( + std::shared_ptr logger, + std::vector>& listeners, + PortInUseCallback portInUse = {}, + ServerFactory serverFactory = {}); + + ~TeamServerSocksService(); + + grpc::Status handleCommand(const std::vector& splitedCmd, teamserverapi::TermCommand* response); + void shutdown(); + void run(); + + bool isRunning() const; + bool isBound() const; + +private: + std::shared_ptr findSessionByPrefix(const std::string& beaconHashPrefix, std::shared_ptr& listener) const; + std::unique_ptr createServer(int port); + bool portInUse(unsigned short port) const; + void unbindThread(); + void stopServer(); + + std::shared_ptr m_logger; + std::vector>& m_listeners; + PortInUseCallback m_portInUse; + ServerFactory m_serverFactory; + + bool m_isSocksServerRunning; + bool m_isSocksServerBinded; + std::unique_ptr m_socksServer; + std::unique_ptr m_socksThread; + std::shared_ptr m_socksListener; + std::shared_ptr m_socksSession; +}; diff --git a/teamServer/tests/TeamServerSocksServiceTests.cpp b/teamServer/tests/TeamServerSocksServiceTests.cpp new file mode 100644 index 0000000..bf51d4b --- /dev/null +++ b/teamServer/tests/TeamServerSocksServiceTests.cpp @@ -0,0 +1,161 @@ +#include +#include +#include +#include + +#include "TeamServerSocksService.hpp" + +namespace +{ +class TestListener final : public Listener +{ +public: + explicit TestListener(const std::string& hash) + : Listener("127.0.0.1", "8443", ListenerHttpsType) + { + m_listenerHash = hash; + } + + std::shared_ptr addSession(const std::string& listenerHash, const std::string& beaconHash, const std::string& os) + { + auto session = std::make_shared(listenerHash, beaconHash, "host", "user", "x64", "admin", os); + m_sessions.push_back(session); + return session; + } +}; + +class FakeSocksServer final : public ISocksServer +{ +public: + explicit FakeSocksServer(bool launched = true, bool stoped = false) + : m_launched(launched), + m_stoped(stoped) + { + } + + void launch() override + { + m_launched = true; + } + + void stop() override + { + m_stoped = true; + } + + void cleanTunnel() override + { + } + + bool isServerStoped() const override + { + return m_stoped; + } + + bool isServerLaunched() const override + { + return m_launched; + } + + std::size_t tunnelCount() override + { + return 0; + } + + SocksTunnelServer* getTunnel(std::size_t) override + { + return nullptr; + } + + void resetTunnel(std::size_t) override + { + } + +private: + bool m_launched; + bool m_stoped; +}; + +std::shared_ptr makeLogger() +{ + auto logger = std::make_shared("socks-tests"); + logger->set_level(spdlog::level::off); + return logger; +} + +void testStartAndStopLifecycle() +{ + std::vector> listeners; + TeamServerSocksService service( + makeLogger(), + listeners, + [](unsigned short) + { return false; }, + [](int) + { return std::make_unique(); }); + + teamserverapi::TermCommand response; + assert(service.handleCommand({"socks", "start"}, &response).ok()); + assert(response.result() == "Socks server successfully started on port 1080"); + assert(service.isRunning()); + + assert(service.handleCommand({"socks", "stop"}, &response).ok()); + assert(response.result() == "Socks server stoped"); + assert(!service.isRunning()); +} + +void testBindAndUnbindLifecycle() +{ + auto listener = std::make_shared("listener-primary"); + listener->addSession("listener-primary", "ABCDEFGH12345678", "Windows"); + std::vector> listeners = {listener}; + + TeamServerSocksService service( + makeLogger(), + listeners, + [](unsigned short) + { return false; }, + [](int) + { return std::make_unique(); }); + + teamserverapi::TermCommand response; + assert(service.handleCommand({"socks", "bind", "ABCDEFGH"}, &response).ok()); + assert(response.result() == "Error: Socks server not running"); + + assert(service.handleCommand({"socks", "start"}, &response).ok()); + assert(service.handleCommand({"socks", "bind", "ABCDEFGH"}, &response).ok()); + assert(response.result().find("Socks server sucessfully binded") != std::string::npos); + assert(service.isBound()); + + assert(service.handleCommand({"socks", "unbind"}, &response).ok()); + assert(response.result() == "Socks server successfully unbinding"); + assert(!service.isBound()); +} + +void testPortInUseAndUnknownCommand() +{ + std::vector> listeners; + TeamServerSocksService service( + makeLogger(), + listeners, + [](unsigned short) + { return true; }, + [](int) + { return std::make_unique(); }); + + teamserverapi::TermCommand response; + assert(service.handleCommand({"socks", "start"}, &response).ok()); + assert(response.result() == "Error: Socks server port already used"); + + assert(service.handleCommand({"socks", "nope"}, &response).ok()); + assert(response.result() == "Error: Socks server command not found."); +} +} // namespace + +int main() +{ + testStartAndStopLifecycle(); + testBindAndUnbindLifecycle(); + testPortInUseAndUnknownCommand(); + return 0; +} From ab3ec45c87dbd6250b8e199fcb3ce9f0e07a9dfd Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 16:06:35 +0200 Subject: [PATCH 13/28] Extract --- core | 2 +- teamServer/CMakeLists.txt | 22 ++ teamServer/teamServer/TeamServer.cpp | 221 +------------- teamServer/teamServer/TeamServer.hpp | 2 + .../teamServer/TeamServerTermLocalService.cpp | 289 ++++++++++++++++++ .../teamServer/TeamServerTermLocalService.hpp | 63 ++++ .../tests/TeamServerTermLocalServiceTests.cpp | 224 ++++++++++++++ 7 files changed, 611 insertions(+), 212 deletions(-) create mode 100644 teamServer/teamServer/TeamServerTermLocalService.cpp create mode 100644 teamServer/teamServer/TeamServerTermLocalService.hpp create mode 100644 teamServer/tests/TeamServerTermLocalServiceTests.cpp diff --git a/core b/core index 1598c69..750248b 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 1598c69281a9435ba8d8a5c89acc91a75cb9800b +Subproject commit 750248b197c6b361366836541b4bbef83f2a0070 diff --git a/teamServer/CMakeLists.txt b/teamServer/CMakeLists.txt index e0becee..5cc48b9 100644 --- a/teamServer/CMakeLists.txt +++ b/teamServer/CMakeLists.txt @@ -7,6 +7,7 @@ teamServer/TeamServer.cpp teamServer/TeamServerAuth.cpp teamServer/TeamServerHelpService.cpp teamServer/TeamServerSocksService.cpp +teamServer/TeamServerTermLocalService.cpp teamServer/TeamServerRuntimeConfig.cpp teamServer/TeamServerBootstrap.cpp teamServer/TeamServerListenerSessionService.cpp @@ -100,6 +101,27 @@ if(WITH_TESTS) add_test(NAME testsTeamServerSocksService COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") + add_executable(testsTeamServerTermLocalService + tests/TeamServerTermLocalServiceTests.cpp + teamServer/TeamServerTermLocalService.cpp + teamServer/TeamServerRuntimeConfig.cpp + ../core/listener/Listener.cpp + ../../thirdParty/base64/base64.cpp + ) + target_include_directories(testsTeamServerTermLocalService PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/teamServer + ) + if(WIN32) + target_link_libraries(testsTeamServerTermLocalService GrpcMessages openssl::openssl ${OPENSSL_CRYPTO_LIBRARY} ZLIB::ZLIB grpc::grpc spdlog::spdlog) + else() + target_link_libraries(testsTeamServerTermLocalService GrpcMessages pthread openssl::openssl ZLIB::ZLIB grpc::grpc spdlog::spdlog httplib::httplib Crow::Crow dl rt) + endif() + + add_custom_command(TARGET testsTeamServerTermLocalService POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $ "${C2_TEST_BIN_OUTPUT_DIR}/$") + + add_test(NAME testsTeamServerTermLocalService COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") + add_executable(testsTeamServerListenerSessionService tests/TeamServerListenerSessionServiceTests.cpp teamServer/TeamServerListenerSessionService.cpp diff --git a/teamServer/teamServer/TeamServer.cpp b/teamServer/teamServer/TeamServer.cpp index abf3b40..1294cb6 100644 --- a/teamServer/teamServer/TeamServer.cpp +++ b/teamServer/teamServer/TeamServer.cpp @@ -5,6 +5,7 @@ #include "TeamServerHelpService.hpp" #include "TeamServerListenerSessionService.hpp" #include "TeamServerSocksService.hpp" +#include "TeamServerTermLocalService.hpp" #include "TeamServerRuntimeConfig.hpp" #include @@ -72,6 +73,13 @@ TeamServer::TeamServer(const nlohmann::json& config) [this](const std::string& input, C2Message& c2Message, bool isWindows) { return this->prepMsg(input, c2Message, isWindows); }); m_socksService = std::make_unique(m_logger, m_listeners); + m_termLocalService = std::make_unique( + m_logger, + m_config, + runtimeConfig, + m_listeners, + m_credentials, + m_moduleCmd); // Modules m_logger->debug("TeamServer module directory path {0}", m_teamServerModulesDirectoryPath.c_str()); @@ -300,12 +308,7 @@ std::string getIPAddress(std::string& interface) const std::string InfoListenerInstruction = "infoListener"; const std::string GetBeaconBinaryInstruction = "getBeaconBinary"; -const std::string PutIntoUploadDirInstruction = "putIntoUploadDir"; -const std::string ReloadModulesInstruction = "reloadModules"; -const std::string BatcaveInstruction = "batcaveUpload"; const std::string InstallInstruction = "install"; -const std::string AddCredentialInstruction = "addCred"; -const std::string GetCredentialInstruction = "getCred"; const std::string SocksInstruction_ = "socks"; grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamserverapi::TermCommand* command, teamserverapi::TermCommand* response) @@ -651,213 +654,9 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser return grpc::Status::OK; } } - else if (instruction == PutIntoUploadDirInstruction) + else if (m_termLocalService->canHandle(instruction)) { - m_logger->debug("putIntoUploadDir {0}", cmd); - - if (splitedCmd.size() == 3) - { - std::string listenerHash = splitedCmd[1]; - - std::string filename = splitedCmd[2]; - if (filename.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-_.") != std::string::npos) - { - responseTmp.set_result("Error: filename not allowed."); - *response = responseTmp; - return grpc::Status::OK; - } - std::string data = command->data(); - - std::string downloadFolder = ""; - for (int i = 0; i < m_listeners.size(); i++) - { - std::string hash = m_listeners[i]->getListenerHash(); - if (hash.find(listenerHash) != std::string::npos) - { - std::string type = m_listeners[i]->getType(); - - try - { - if (type == ListenerHttpType) - { - json configHttp = m_config["ListenerHttpConfig"]; - - auto it = configHttp.find("downloadFolder"); - if (it != configHttp.end()) - downloadFolder = configHttp["downloadFolder"].get(); - ; - } - else if (type == ListenerHttpsType) - { - json configHttps = m_config["ListenerHttpsConfig"]; - - auto it = configHttps.find("downloadFolder"); - if (it != configHttps.end()) - downloadFolder = configHttps["downloadFolder"].get(); - ; - } - } - catch (...) - { - responseTmp.set_result("Error: Value not found in config file."); - } - } - } - - if (!downloadFolder.empty()) - { - std::string filePath = downloadFolder; - filePath += "/"; - filePath += filename; - - ofstream outputFile(filePath, ios::out | ios::binary); - if (outputFile.good()) - { - outputFile << data; - outputFile.close(); - responseTmp.set_result("ok"); - m_logger->info("Stored uploaded file '{0}' for listener {1} in {2}", filename, listenerHash, filePath); - } - else - { - responseTmp.set_result("Error: Cannot write file."); - m_logger->warn("Failed to store uploaded file '{0}' for listener {1} in {2}", filename, listenerHash, filePath); - } - } - else - { - responseTmp.set_result("Error: Listener don't have a download folder."); - m_logger->warn("Listener {0} has no download folder configured; unable to store {1}", listenerHash, filename); - } - } - else - { - responseTmp.set_result("Error: putIntoUploadDir take tow arguements."); - *response = responseTmp; - return grpc::Status::OK; - } - } - else if (instruction == BatcaveInstruction) - { - m_logger->debug("batcaveUpload {0}", cmd); - if (splitedCmd.size() == 2) - { - std::string filename = splitedCmd[1]; - m_logger->debug("batcaveUpload {0}", filename); - if (filename.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-_.") != std::string::npos) - { - responseTmp.set_result("Error: filename not allowed."); - *response = responseTmp; - return grpc::Status::OK; - } - std::string data = command->data(); - std::string filePath = m_toolsDirectoryPath; - filePath += "/"; - filePath += filename; - - ofstream outputFile(filePath, ios::out | ios::binary); - if (outputFile.good()) - { - outputFile << data; - outputFile.close(); - responseTmp.set_result("ok"); - m_logger->info("Saved uploaded tool '{0}' to {1}", filename, filePath); - } - else - { - responseTmp.set_result("Error: Cannot write file."); - m_logger->warn("Failed to store uploaded tool '{0}' at {1}", filename, filePath); - } - return grpc::Status::OK; - } - } - // TODO handle some sort of backup - else if (instruction == AddCredentialInstruction) - { - m_logger->debug("AddCredentials command received"); - - std::string data = command->data(); - json cred = json::parse(data); - m_credentials.push_back(cred); - m_logger->info("Stored credential entry. Total credentials: {0}", m_credentials.size()); - responseTmp.set_result("ok"); - return grpc::Status::OK; - } - else if (instruction == GetCredentialInstruction) - { - m_logger->debug("GetCredentials command received"); - - responseTmp.set_result(m_credentials.dump()); - *response = responseTmp; - return grpc::Status::OK; - } - // TODO - else if (instruction == ReloadModulesInstruction) - { - m_logger->info("Reloading TeamServer modules from directory: {0}", m_teamServerModulesDirectoryPath.c_str()); - - // Clear previously loaded modules - m_moduleCmd.clear(); - std::size_t reloadedModules = 0; - - try - { - for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath)) - { - if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".so") - { - m_logger->debug("Trying to load {0}", entry.path().c_str()); - - void* handle = dlopen(entry.path().c_str(), RTLD_LAZY); - if (!handle) - { - m_logger->warn("Failed to load {0}: {1}", entry.path().c_str(), dlerror()); - continue; - } - - // Derive constructor function name - std::string funcName = entry.path().filename(); - funcName = funcName.substr(3); // remove lib - funcName = funcName.substr(0, funcName.length() - 3); // remove .so - funcName += "Constructor"; // add Constructor - - m_logger->debug("Looking for constructor function: {0}", funcName); - - constructProc construct = (constructProc)dlsym(handle, funcName.c_str()); - if (!construct) - { - m_logger->warn("Failed to find constructor: {0}", dlerror()); - dlclose(handle); - continue; - } - - ModuleCmd* moduleCmd = construct(); - if (!moduleCmd) - { - m_logger->warn("Constructor returned null"); - dlclose(handle); - continue; - } - - std::unique_ptr moduleCmdPtr(moduleCmd); - TeamServerRuntimeConfig runtimeConfig = TeamServerRuntimeConfig::fromJson(m_config); - runtimeConfig.configureModule(*moduleCmdPtr); - - m_logger->debug("Module {0} loaded", entry.path().filename().c_str()); - m_moduleCmd.push_back(std::move(moduleCmdPtr)); - reloadedModules++; - } - } - } - catch (const std::filesystem::filesystem_error& e) - { - m_logger->warn("Error accessing module directory: {0}", e.what()); - } - - if (reloadedModules == 0) - m_logger->warn("No TeamServer modules loaded from {0}", m_teamServerModulesDirectoryPath.c_str()); - else - m_logger->info("Reloaded {0} TeamServer module(s) from {1}", reloadedModules, m_teamServerModulesDirectoryPath.c_str()); + return m_termLocalService->handleCommand(instruction, splitedCmd, *command, response); } else if (instruction == SocksInstruction_) { diff --git a/teamServer/teamServer/TeamServer.hpp b/teamServer/teamServer/TeamServer.hpp index 29e0806..7618cf0 100644 --- a/teamServer/teamServer/TeamServer.hpp +++ b/teamServer/teamServer/TeamServer.hpp @@ -32,6 +32,7 @@ class TeamServerAuthManager; class TeamServerHelpService; class TeamServerListenerSessionService; class TeamServerSocksService; +class TeamServerTermLocalService; class TeamServer final : public teamserverapi::TeamServerApi::Service { @@ -92,4 +93,5 @@ class TeamServer final : public teamserverapi::TeamServerApi::Service std::unique_ptr m_helpService; std::unique_ptr m_listenerSessionService; std::unique_ptr m_socksService; + std::unique_ptr m_termLocalService; }; diff --git a/teamServer/teamServer/TeamServerTermLocalService.cpp b/teamServer/teamServer/TeamServerTermLocalService.cpp new file mode 100644 index 0000000..b9513c2 --- /dev/null +++ b/teamServer/teamServer/TeamServerTermLocalService.cpp @@ -0,0 +1,289 @@ +#include "TeamServerTermLocalService.hpp" + +#include + +#include +#include + +#include "listener/ListenerHttp.hpp" + +namespace fs = std::filesystem; +using json = nlohmann::json; + +namespace +{ +using constructProc = ModuleCmd* (*)(); + +const std::string PutIntoUploadDirInstruction = "putIntoUploadDir"; +const std::string ReloadModulesInstruction = "reloadModules"; +const std::string BatcaveInstruction = "batcaveUpload"; +const std::string AddCredentialInstruction = "addCred"; +const std::string GetCredentialInstruction = "getCred"; +} // namespace + +TeamServerTermLocalService::TeamServerTermLocalService( + std::shared_ptr logger, + const nlohmann::json& config, + TeamServerRuntimeConfig runtimeConfig, + std::vector>& listeners, + nlohmann::json& credentials, + std::vector>& moduleCmd, + ModuleLoader moduleLoader) + : m_logger(std::move(logger)), + m_config(config), + m_runtimeConfig(std::move(runtimeConfig)), + m_listeners(listeners), + m_credentials(credentials), + m_moduleCmd(moduleCmd), + m_moduleLoader(std::move(moduleLoader)) +{ +} + +bool TeamServerTermLocalService::canHandle(const std::string& instruction) const +{ + return instruction == PutIntoUploadDirInstruction + || instruction == BatcaveInstruction + || instruction == AddCredentialInstruction + || instruction == GetCredentialInstruction + || instruction == ReloadModulesInstruction; +} + +grpc::Status TeamServerTermLocalService::handleCommand( + const std::string& instruction, + const std::vector& splitedCmd, + const teamserverapi::TermCommand& command, + teamserverapi::TermCommand* response) +{ + response->set_cmd(""); + response->set_result(""); + response->set_data(""); + + if (instruction == PutIntoUploadDirInstruction) + return handlePutIntoUploadDir(splitedCmd, command, response); + if (instruction == BatcaveInstruction) + return handleBatcaveUpload(splitedCmd, command, response); + if (instruction == AddCredentialInstruction) + return handleAddCredential(command, response); + if (instruction == GetCredentialInstruction) + return handleGetCredential(response); + if (instruction == ReloadModulesInstruction) + return handleReloadModules(response); + + response->set_result("Error: not implemented."); + return grpc::Status::OK; +} + +std::vector> TeamServerTermLocalService::loadModulesFromDisk() const +{ + std::vector> modules; + + try + { + for (const auto& entry : fs::recursive_directory_iterator(m_runtimeConfig.teamServerModulesDirectoryPath)) + { + if (!fs::is_regular_file(entry.path()) || entry.path().extension() != ".so") + continue; + + m_logger->debug("Trying to load {0}", entry.path().c_str()); + + void* handle = dlopen(entry.path().c_str(), RTLD_LAZY); + if (!handle) + { + m_logger->warn("Failed to load {0}: {1}", entry.path().c_str(), dlerror()); + continue; + } + + std::string funcName = entry.path().filename(); + funcName = funcName.substr(3); + funcName = funcName.substr(0, funcName.length() - 3); + funcName += "Constructor"; + + m_logger->debug("Looking for constructor function: {0}", funcName); + + constructProc construct = reinterpret_cast(dlsym(handle, funcName.c_str())); + if (!construct) + { + m_logger->warn("Failed to find constructor: {0}", dlerror()); + dlclose(handle); + continue; + } + + ModuleCmd* moduleCmd = construct(); + if (!moduleCmd) + { + m_logger->warn("Constructor returned null"); + dlclose(handle); + continue; + } + + std::unique_ptr moduleCmdPtr(moduleCmd); + m_runtimeConfig.configureModule(*moduleCmdPtr); + m_logger->debug("Module {0} loaded", entry.path().filename().c_str()); + modules.push_back(std::move(moduleCmdPtr)); + } + } + catch (const fs::filesystem_error& e) + { + m_logger->warn("Error accessing module directory: {0}", e.what()); + } + + return modules; +} + +bool TeamServerTermLocalService::isValidFilename(const std::string& filename) const +{ + return filename.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-_.") == std::string::npos; +} + +std::string TeamServerTermLocalService::resolveDownloadFolderForListener(const std::string& listenerHash) const +{ + std::string downloadFolder; + for (const auto& listener : m_listeners) + { + const std::string& hash = listener->getListenerHash(); + if (hash.find(listenerHash) == std::string::npos) + continue; + + const std::string& type = listener->getType(); + try + { + if (type == ListenerHttpType) + { + json configHttp = m_config["ListenerHttpConfig"]; + auto it = configHttp.find("downloadFolder"); + if (it != configHttp.end()) + downloadFolder = configHttp["downloadFolder"].get(); + } + else if (type == ListenerHttpsType) + { + json configHttps = m_config["ListenerHttpsConfig"]; + auto it = configHttps.find("downloadFolder"); + if (it != configHttps.end()) + downloadFolder = configHttps["downloadFolder"].get(); + } + } + catch (...) + { + return ""; + } + } + + return downloadFolder; +} + +grpc::Status TeamServerTermLocalService::handlePutIntoUploadDir( + const std::vector& splitedCmd, + const teamserverapi::TermCommand& command, + teamserverapi::TermCommand* response) +{ + m_logger->debug("putIntoUploadDir {0}", command.cmd()); + + if (splitedCmd.size() != 3) + { + response->set_result("Error: putIntoUploadDir take tow arguements."); + return grpc::Status::OK; + } + + const std::string& listenerHash = splitedCmd[1]; + const std::string& filename = splitedCmd[2]; + if (!isValidFilename(filename)) + { + response->set_result("Error: filename not allowed."); + return grpc::Status::OK; + } + + const std::string downloadFolder = resolveDownloadFolderForListener(listenerHash); + if (downloadFolder.empty()) + { + response->set_result("Error: Listener don't have a download folder."); + m_logger->warn("Listener {0} has no download folder configured; unable to store {1}", listenerHash, filename); + return grpc::Status::OK; + } + + const std::string filePath = downloadFolder + "/" + filename; + std::ofstream outputFile(filePath, std::ios::out | std::ios::binary); + if (outputFile.good()) + { + outputFile << command.data(); + outputFile.close(); + response->set_result("ok"); + m_logger->info("Stored uploaded file '{0}' for listener {1} in {2}", filename, listenerHash, filePath); + return grpc::Status::OK; + } + + response->set_result("Error: Cannot write file."); + m_logger->warn("Failed to store uploaded file '{0}' for listener {1} in {2}", filename, listenerHash, filePath); + return grpc::Status::OK; +} + +grpc::Status TeamServerTermLocalService::handleBatcaveUpload( + const std::vector& splitedCmd, + const teamserverapi::TermCommand& command, + teamserverapi::TermCommand* response) +{ + m_logger->debug("batcaveUpload {0}", command.cmd()); + + if (splitedCmd.size() != 2) + return grpc::Status::OK; + + const std::string& filename = splitedCmd[1]; + m_logger->debug("batcaveUpload {0}", filename); + if (!isValidFilename(filename)) + { + response->set_result("Error: filename not allowed."); + return grpc::Status::OK; + } + + const std::string filePath = m_runtimeConfig.toolsDirectoryPath + "/" + filename; + std::ofstream outputFile(filePath, std::ios::out | std::ios::binary); + if (outputFile.good()) + { + outputFile << command.data(); + outputFile.close(); + response->set_result("ok"); + m_logger->info("Saved uploaded tool '{0}' to {1}", filename, filePath); + return grpc::Status::OK; + } + + response->set_result("Error: Cannot write file."); + m_logger->warn("Failed to store uploaded tool '{0}' at {1}", filename, filePath); + return grpc::Status::OK; +} + +grpc::Status TeamServerTermLocalService::handleAddCredential( + const teamserverapi::TermCommand& command, + teamserverapi::TermCommand* response) +{ + m_logger->debug("AddCredentials command received"); + + json cred = json::parse(command.data()); + m_credentials.push_back(cred); + m_logger->info("Stored credential entry. Total credentials: {0}", m_credentials.size()); + response->set_result("ok"); + return grpc::Status::OK; +} + +grpc::Status TeamServerTermLocalService::handleGetCredential(teamserverapi::TermCommand* response) +{ + m_logger->debug("GetCredentials command received"); + response->set_result(m_credentials.dump()); + return grpc::Status::OK; +} + +grpc::Status TeamServerTermLocalService::handleReloadModules(teamserverapi::TermCommand* response) +{ + (void)response; + m_logger->info("Reloading TeamServer modules from directory: {0}", m_runtimeConfig.teamServerModulesDirectoryPath.c_str()); + + m_moduleCmd.clear(); + std::vector> reloaded = m_moduleLoader ? m_moduleLoader() : loadModulesFromDisk(); + const std::size_t reloadedModules = reloaded.size(); + m_moduleCmd = std::move(reloaded); + + if (reloadedModules == 0) + m_logger->warn("No TeamServer modules loaded from {0}", m_runtimeConfig.teamServerModulesDirectoryPath.c_str()); + else + m_logger->info("Reloaded {0} TeamServer module(s) from {1}", reloadedModules, m_runtimeConfig.teamServerModulesDirectoryPath.c_str()); + + return grpc::Status::OK; +} diff --git a/teamServer/teamServer/TeamServerTermLocalService.hpp b/teamServer/teamServer/TeamServerTermLocalService.hpp new file mode 100644 index 0000000..d73005c --- /dev/null +++ b/teamServer/teamServer/TeamServerTermLocalService.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include "TeamServerApi.pb.h" +#include "TeamServerRuntimeConfig.hpp" +#include "listener/Listener.hpp" +#include "modules/ModuleCmd/ModuleCmd.hpp" +#include "nlohmann/json.hpp" +#include "spdlog/logger.h" + +class TeamServerTermLocalService +{ +public: + using ModuleLoader = std::function>()>; + + TeamServerTermLocalService( + std::shared_ptr logger, + const nlohmann::json& config, + TeamServerRuntimeConfig runtimeConfig, + std::vector>& listeners, + nlohmann::json& credentials, + std::vector>& moduleCmd, + ModuleLoader moduleLoader = {}); + + bool canHandle(const std::string& instruction) const; + grpc::Status handleCommand( + const std::string& instruction, + const std::vector& splitedCmd, + const teamserverapi::TermCommand& command, + teamserverapi::TermCommand* response); + +private: + std::vector> loadModulesFromDisk() const; + bool isValidFilename(const std::string& filename) const; + std::string resolveDownloadFolderForListener(const std::string& listenerHash) const; + grpc::Status handlePutIntoUploadDir( + const std::vector& splitedCmd, + const teamserverapi::TermCommand& command, + teamserverapi::TermCommand* response); + grpc::Status handleBatcaveUpload( + const std::vector& splitedCmd, + const teamserverapi::TermCommand& command, + teamserverapi::TermCommand* response); + grpc::Status handleAddCredential( + const teamserverapi::TermCommand& command, + teamserverapi::TermCommand* response); + grpc::Status handleGetCredential(teamserverapi::TermCommand* response); + grpc::Status handleReloadModules(teamserverapi::TermCommand* response); + + std::shared_ptr m_logger; + const nlohmann::json& m_config; + TeamServerRuntimeConfig m_runtimeConfig; + std::vector>& m_listeners; + nlohmann::json& m_credentials; + std::vector>& m_moduleCmd; + ModuleLoader m_moduleLoader; +}; diff --git a/teamServer/tests/TeamServerTermLocalServiceTests.cpp b/teamServer/tests/TeamServerTermLocalServiceTests.cpp new file mode 100644 index 0000000..4d630ac --- /dev/null +++ b/teamServer/tests/TeamServerTermLocalServiceTests.cpp @@ -0,0 +1,224 @@ +#include +#include +#include +#include +#include +#include + +#include "TeamServerTermLocalService.hpp" + +namespace fs = std::filesystem; + +namespace +{ +class ScopedPath +{ +public: + explicit ScopedPath(fs::path path) + : m_path(std::move(path)) + { + } + + ~ScopedPath() + { + std::error_code ec; + fs::remove_all(m_path, ec); + } + + const fs::path& path() const + { + return m_path; + } + +private: + fs::path m_path; +}; + +class TestListener final : public Listener +{ +public: + explicit TestListener(const std::string& hash) + : Listener("127.0.0.1", "8443", ListenerHttpsType) + { + m_listenerHash = hash; + } +}; + +class FakeModule final : public ModuleCmd +{ +public: + explicit FakeModule(std::string name) + : ModuleCmd(std::move(name)) + { + } + + std::string getInfo() override + { + return "fake module"; + } + + int init(std::vector&, C2Message&) override + { + return 0; + } + + int process(C2Message&, C2Message&) override + { + return 0; + } +}; + +fs::path makeTempDirectory(const std::string& name) +{ + fs::path root = fs::temp_directory_path() / ("c2teamserver-term-local-" + name + "-" + std::to_string(::getpid())); + fs::create_directories(root); + return root; +} + +std::shared_ptr makeLogger() +{ + auto logger = std::make_shared("term-local-tests"); + logger->set_level(spdlog::level::off); + return logger; +} + +TeamServerRuntimeConfig makeRuntimeConfig(const fs::path& root) +{ + TeamServerRuntimeConfig runtimeConfig; + runtimeConfig.teamServerModulesDirectoryPath = (root / "modules").string(); + runtimeConfig.linuxModulesDirectoryPath = (root / "linux-modules").string(); + runtimeConfig.windowsModulesDirectoryPath = (root / "windows-modules").string(); + runtimeConfig.linuxBeaconsDirectoryPath = (root / "linux-beacons").string(); + runtimeConfig.windowsBeaconsDirectoryPath = (root / "windows-beacons").string(); + runtimeConfig.toolsDirectoryPath = (root / "tools").string(); + runtimeConfig.scriptsDirectoryPath = (root / "scripts").string(); + + fs::create_directories(runtimeConfig.teamServerModulesDirectoryPath); + fs::create_directories(runtimeConfig.linuxModulesDirectoryPath); + fs::create_directories(runtimeConfig.windowsModulesDirectoryPath); + fs::create_directories(runtimeConfig.linuxBeaconsDirectoryPath); + fs::create_directories(runtimeConfig.windowsBeaconsDirectoryPath); + fs::create_directories(runtimeConfig.toolsDirectoryPath); + fs::create_directories(runtimeConfig.scriptsDirectoryPath); + + return runtimeConfig; +} + +std::string readFile(const fs::path& path) +{ + std::ifstream input(path, std::ios::binary); + return std::string((std::istreambuf_iterator(input)), std::istreambuf_iterator()); +} + +void testUploadCommands() +{ + ScopedPath tempRoot(makeTempDirectory("upload")); + TeamServerRuntimeConfig runtimeConfig = makeRuntimeConfig(tempRoot.path()); + fs::path downloadDir = tempRoot.path() / "downloads"; + fs::create_directories(downloadDir); + + nlohmann::json config = { + {"ListenerHttpsConfig", {{"downloadFolder", downloadDir.string()}}}}; + std::vector> listeners; + listeners.push_back(std::make_shared("listener-primary")); + nlohmann::json credentials = nlohmann::json::array(); + std::vector> modules; + + TeamServerTermLocalService service( + makeLogger(), + config, + runtimeConfig, + listeners, + credentials, + modules); + + teamserverapi::TermCommand uploadCommand; + uploadCommand.set_cmd("putIntoUploadDir listener-pri hello.bin"); + uploadCommand.set_data("PAYLOAD"); + + teamserverapi::TermCommand response; + assert(service.handleCommand("putIntoUploadDir", {"putIntoUploadDir", "listener-pri", "hello.bin"}, uploadCommand, &response).ok()); + assert(response.result() == "ok"); + assert(readFile(downloadDir / "hello.bin") == "PAYLOAD"); + + teamserverapi::TermCommand batcaveCommand; + batcaveCommand.set_cmd("batcaveUpload tool.bin"); + batcaveCommand.set_data("TOOL"); + assert(service.handleCommand("batcaveUpload", {"batcaveUpload", "tool.bin"}, batcaveCommand, &response).ok()); + assert(response.result() == "ok"); + assert(readFile(fs::path(runtimeConfig.toolsDirectoryPath) / "tool.bin") == "TOOL"); +} + +void testCredentialCommands() +{ + ScopedPath tempRoot(makeTempDirectory("cred")); + TeamServerRuntimeConfig runtimeConfig = makeRuntimeConfig(tempRoot.path()); + nlohmann::json config = nlohmann::json::object(); + std::vector> listeners; + nlohmann::json credentials = nlohmann::json::array(); + std::vector> modules; + + TeamServerTermLocalService service( + makeLogger(), + config, + runtimeConfig, + listeners, + credentials, + modules); + + teamserverapi::TermCommand addCommand; + addCommand.set_cmd("addCred"); + addCommand.set_data(R"({"username":"alice","password":"secret"})"); + + teamserverapi::TermCommand response; + assert(service.handleCommand("addCred", {"addCred"}, addCommand, &response).ok()); + assert(response.result() == "ok"); + assert(credentials.size() == 1); + + teamserverapi::TermCommand getCommand; + getCommand.set_cmd("getCred"); + assert(service.handleCommand("getCred", {"getCred"}, getCommand, &response).ok()); + assert(response.result().find("alice") != std::string::npos); +} + +void testReloadModulesUsesInjectedLoader() +{ + ScopedPath tempRoot(makeTempDirectory("reload")); + TeamServerRuntimeConfig runtimeConfig = makeRuntimeConfig(tempRoot.path()); + nlohmann::json config = nlohmann::json::object(); + std::vector> listeners; + nlohmann::json credentials = nlohmann::json::array(); + std::vector> modules; + modules.push_back(std::make_unique("OldModule")); + + TeamServerTermLocalService service( + makeLogger(), + config, + runtimeConfig, + listeners, + credentials, + modules, + []() + { + std::vector> loaded; + loaded.push_back(std::make_unique("ReloadedModule")); + return loaded; + }); + + teamserverapi::TermCommand command; + command.set_cmd("reloadModules"); + teamserverapi::TermCommand response; + assert(service.handleCommand("reloadModules", {"reloadModules"}, command, &response).ok()); + assert(modules.size() == 1); + assert(modules.front()->getName() == "ReloadedModule"); + assert(response.result().empty()); +} +} // namespace + +int main() +{ + testUploadCommands(); + testCredentialCommands(); + testReloadModulesUsesInjectedLoader(); + return 0; +} From 912c06287e5045acadab94aee55764a2446b821e Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:52:32 +0200 Subject: [PATCH 14/28] clean --- Release/.gitignore | 11 ----------- Release/Beacons/.gitignore | 3 --- Release/Modules/.gitignore | 2 -- Release/Scripts/.gitignore | 2 -- Release/TeamServer/.gitignore | 2 -- Release/TeamServer/logs/.gitignore | 2 -- Release/Tools/.gitignore | 2 -- Release/www/.gitignore | 2 -- 8 files changed, 26 deletions(-) delete mode 100644 Release/.gitignore delete mode 100644 Release/Beacons/.gitignore delete mode 100644 Release/Modules/.gitignore delete mode 100644 Release/Scripts/.gitignore delete mode 100644 Release/TeamServer/.gitignore delete mode 100644 Release/TeamServer/logs/.gitignore delete mode 100644 Release/Tools/.gitignore delete mode 100644 Release/www/.gitignore diff --git a/Release/.gitignore b/Release/.gitignore deleted file mode 100644 index 5367063..0000000 --- a/Release/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -* - -!.gitignore - -!www -!Tools -!TeamServer -!Modules -!Scripts -!Client -!Beacons \ No newline at end of file diff --git a/Release/Beacons/.gitignore b/Release/Beacons/.gitignore deleted file mode 100644 index a5baada..0000000 --- a/Release/Beacons/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore - diff --git a/Release/Modules/.gitignore b/Release/Modules/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/Release/Modules/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/Release/Scripts/.gitignore b/Release/Scripts/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/Release/Scripts/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/Release/TeamServer/.gitignore b/Release/TeamServer/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/Release/TeamServer/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/Release/TeamServer/logs/.gitignore b/Release/TeamServer/logs/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/Release/TeamServer/logs/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/Release/Tools/.gitignore b/Release/Tools/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/Release/Tools/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/Release/www/.gitignore b/Release/www/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/Release/www/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file From b4a9a9a578959bdae7bf5c2d23aca84e715435fc Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 18:33:42 +0200 Subject: [PATCH 15/28] update dep --- conanfile.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/conanfile.txt b/conanfile.txt index 60102bb..e908308 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,9 +1,9 @@ [requires] -grpc/1.72.0 -protobuf/5.27.0 -spdlog/1.15.3 -cpp-httplib/0.20.1 -openssl/3.5.1 +grpc/1.78.1 +protobuf/6.33.5 +spdlog/1.17.0 +cpp-httplib/0.39.0 +openssl/3.6.2 crowcpp-crow/1.3.0 [layout] From e55f39dcb35d13ae309f5f878036771488367cdb Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:28:34 +0200 Subject: [PATCH 16/28] Finish --- C2Client/pyproject.toml | 2 +- CMakeLists.txt | 14 +- protocol/CMakeLists.txt | 4 +- teamServer/CMakeLists.txt | 43 ++ teamServer/teamServer/TeamServer.cpp | 537 +----------------- teamServer/teamServer/TeamServer.hpp | 14 +- .../TeamServerCommandPreparationService.cpp | 143 +++++ .../TeamServerCommandPreparationService.hpp | 30 + .../TeamServerListenerArtifactService.cpp | 259 +++++++++ .../TeamServerListenerArtifactService.hpp | 53 ++ .../teamServer/TeamServerModuleLoader.cpp | 84 +++ .../teamServer/TeamServerModuleLoader.hpp | 22 + .../teamServer/TeamServerTermLocalService.cpp | 63 +- ...amServerCommandPreparationServiceTests.cpp | 133 +++++ ...TeamServerListenerArtifactServiceTests.cpp | 155 +++++ 15 files changed, 971 insertions(+), 585 deletions(-) create mode 100644 teamServer/teamServer/TeamServerCommandPreparationService.cpp create mode 100644 teamServer/teamServer/TeamServerCommandPreparationService.hpp create mode 100644 teamServer/teamServer/TeamServerListenerArtifactService.cpp create mode 100644 teamServer/teamServer/TeamServerListenerArtifactService.hpp create mode 100644 teamServer/teamServer/TeamServerModuleLoader.cpp create mode 100644 teamServer/teamServer/TeamServerModuleLoader.hpp create mode 100644 teamServer/tests/TeamServerCommandPreparationServiceTests.cpp create mode 100644 teamServer/tests/TeamServerListenerArtifactServiceTests.cpp diff --git a/C2Client/pyproject.toml b/C2Client/pyproject.toml index 959aa46..6a5cf1c 100644 --- a/C2Client/pyproject.toml +++ b/C2Client/pyproject.toml @@ -11,7 +11,7 @@ dependencies = [ "grpcio==1.74.0", "PyQt6==6.7.0", "pyqtdarktheme", - "protobuf==6.32.0", + "protobuf==6.33.5", "gitpython==3.1.45", "requests==2.32.5", "pwn==1.0", diff --git a/CMakeLists.txt b/CMakeLists.txt index 5981e71..ae8b85b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,11 +26,15 @@ file(MAKE_DIRECTORY ## Conan Dependencies ## -set(CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR}) - -find_package(gRPC REQUIRED) -find_package(OpenSSL REQUIRED) -find_package(protobuf REQUIRED) +set(CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR}) + +unset(Protobuf_PROTOC_EXECUTABLE CACHE) +unset(GRPC_CPP_PLUGIN_PROGRAM CACHE) +unset(GRPC_PYTHON_PLUGIN_PROGRAM CACHE) + +find_package(gRPC REQUIRED) +find_package(OpenSSL REQUIRED) +find_package(protobuf REQUIRED) find_package(ZLIB REQUIRED) find_package(spdlog REQUIRED) find_package(httplib REQUIRED) diff --git a/protocol/CMakeLists.txt b/protocol/CMakeLists.txt index 06df85b..9788058 100644 --- a/protocol/CMakeLists.txt +++ b/protocol/CMakeLists.txt @@ -31,7 +31,7 @@ add_custom_command( --grpc_out=${C2_PROTOCOL_CPP_OUT_DIR} --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN_PROGRAM} ${C2_PROTOCOL_PROTO_FILE} - DEPENDS ${C2_PROTOCOL_PROTO_FILE} + DEPENDS ${C2_PROTOCOL_PROTO_FILE} ${Protobuf_PROTOC_EXECUTABLE} ${GRPC_CPP_PLUGIN_PROGRAM} VERBATIM ) @@ -44,7 +44,7 @@ add_custom_command( --grpc_out=${C2_GENERATED_PYTHON_GRPC_DIR} --plugin=protoc-gen-grpc=${GRPC_PYTHON_PLUGIN_PROGRAM} ${C2_PROTOCOL_PROTO_FILE} - DEPENDS ${C2_PROTOCOL_PROTO_FILE} + DEPENDS ${C2_PROTOCOL_PROTO_FILE} ${Protobuf_PROTOC_EXECUTABLE} ${GRPC_PYTHON_PLUGIN_PROGRAM} VERBATIM ) diff --git a/teamServer/CMakeLists.txt b/teamServer/CMakeLists.txt index 5cc48b9..2753562 100644 --- a/teamServer/CMakeLists.txt +++ b/teamServer/CMakeLists.txt @@ -5,7 +5,10 @@ include_directories(../core/modules/ModuleCmd) set(SOURCES_TEAMSERVER teamServer/TeamServer.cpp teamServer/TeamServerAuth.cpp +teamServer/TeamServerCommandPreparationService.cpp teamServer/TeamServerHelpService.cpp +teamServer/TeamServerListenerArtifactService.cpp +teamServer/TeamServerModuleLoader.cpp teamServer/TeamServerSocksService.cpp teamServer/TeamServerTermLocalService.cpp teamServer/TeamServerRuntimeConfig.cpp @@ -80,6 +83,45 @@ if(WITH_TESTS) add_test(NAME testsTeamServerHelpService COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") + add_executable(testsTeamServerCommandPreparationService + tests/TeamServerCommandPreparationServiceTests.cpp + teamServer/TeamServerCommandPreparationService.cpp + ../../thirdParty/base64/base64.cpp + ) + target_include_directories(testsTeamServerCommandPreparationService PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/teamServer + ) + if(WIN32) + target_link_libraries(testsTeamServerCommandPreparationService GrpcMessages openssl::openssl ${OPENSSL_CRYPTO_LIBRARY} ZLIB::ZLIB grpc::grpc spdlog::spdlog) + else() + target_link_libraries(testsTeamServerCommandPreparationService GrpcMessages pthread openssl::openssl ZLIB::ZLIB grpc::grpc spdlog::spdlog httplib::httplib Crow::Crow dl rt) + endif() + + add_custom_command(TARGET testsTeamServerCommandPreparationService POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $ "${C2_TEST_BIN_OUTPUT_DIR}/$") + + add_test(NAME testsTeamServerCommandPreparationService COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") + + add_executable(testsTeamServerListenerArtifactService + tests/TeamServerListenerArtifactServiceTests.cpp + teamServer/TeamServerListenerArtifactService.cpp + ../core/listener/Listener.cpp + ../../thirdParty/base64/base64.cpp + ) + target_include_directories(testsTeamServerListenerArtifactService PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/teamServer + ) + if(WIN32) + target_link_libraries(testsTeamServerListenerArtifactService GrpcMessages openssl::openssl ${OPENSSL_CRYPTO_LIBRARY} ZLIB::ZLIB grpc::grpc spdlog::spdlog) + else() + target_link_libraries(testsTeamServerListenerArtifactService GrpcMessages pthread openssl::openssl ZLIB::ZLIB grpc::grpc spdlog::spdlog httplib::httplib Crow::Crow dl rt) + endif() + + add_custom_command(TARGET testsTeamServerListenerArtifactService POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $ "${C2_TEST_BIN_OUTPUT_DIR}/$") + + add_test(NAME testsTeamServerListenerArtifactService COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") + add_executable(testsTeamServerSocksService tests/TeamServerSocksServiceTests.cpp teamServer/TeamServerSocksService.cpp @@ -103,6 +145,7 @@ if(WITH_TESTS) add_executable(testsTeamServerTermLocalService tests/TeamServerTermLocalServiceTests.cpp + teamServer/TeamServerModuleLoader.cpp teamServer/TeamServerTermLocalService.cpp teamServer/TeamServerRuntimeConfig.cpp ../core/listener/Listener.cpp diff --git a/teamServer/teamServer/TeamServer.cpp b/teamServer/teamServer/TeamServer.cpp index 1294cb6..2d97028 100644 --- a/teamServer/teamServer/TeamServer.cpp +++ b/teamServer/teamServer/TeamServer.cpp @@ -2,36 +2,35 @@ #include "TeamServerAuth.hpp" #include "TeamServerBootstrap.hpp" +#include "TeamServerCommandPreparationService.hpp" #include "TeamServerHelpService.hpp" +#include "TeamServerListenerArtifactService.hpp" #include "TeamServerListenerSessionService.hpp" +#include "TeamServerModuleLoader.hpp" #include "TeamServerSocksService.hpp" #include "TeamServerTermLocalService.hpp" #include "TeamServerRuntimeConfig.hpp" -#include - #include #include #include -#include #include #include #include using namespace std; using namespace std::placeholders; -namespace fs = std::filesystem; using json = nlohmann::json; -typedef ModuleCmd* (*constructProc)(); - inline bool port_in_use(unsigned short port) { return 0; } +std::string getIPAddress(const std::string& interface); + grpc::Status TeamServer::ensureAuthenticated(grpc::ServerContext* context) { return m_authManager->ensureAuthenticated(context->client_metadata()); @@ -43,14 +42,6 @@ TeamServer::TeamServer(const nlohmann::json& config) m_logger = createTeamServerLogger(config); TeamServerRuntimeConfig runtimeConfig = TeamServerRuntimeConfig::fromJson(config); - m_teamServerModulesDirectoryPath = runtimeConfig.teamServerModulesDirectoryPath; - m_linuxModulesDirectoryPath = runtimeConfig.linuxModulesDirectoryPath; - m_windowsModulesDirectoryPath = runtimeConfig.windowsModulesDirectoryPath; - m_linuxBeaconsDirectoryPath = runtimeConfig.linuxBeaconsDirectoryPath; - m_windowsBeaconsDirectoryPath = runtimeConfig.windowsBeaconsDirectoryPath; - m_toolsDirectoryPath = runtimeConfig.toolsDirectoryPath; - m_scriptsDirectoryPath = runtimeConfig.scriptsDirectoryPath; - runtimeConfig.validateDirectories(m_logger); runtimeConfig.configureCommonCommands(m_commonCommands); @@ -72,70 +63,33 @@ TeamServer::TeamServer(const nlohmann::json& config) m_sentC2Messages, [this](const std::string& input, C2Message& c2Message, bool isWindows) { return this->prepMsg(input, c2Message, isWindows); }); + m_listenerArtifactService = std::make_unique( + m_logger, + m_config, + runtimeConfig, + m_listeners, + [](const std::string& interface) + { + return getIPAddress(interface); + }); + m_moduleLoader = std::make_unique(m_logger, runtimeConfig); m_socksService = std::make_unique(m_logger, m_listeners); + m_commandPreparationService = std::make_unique( + m_logger, + runtimeConfig.teamServerModulesDirectoryPath, + m_commonCommands, + m_moduleCmd); m_termLocalService = std::make_unique( m_logger, m_config, runtimeConfig, m_listeners, m_credentials, - m_moduleCmd); - - // Modules - m_logger->debug("TeamServer module directory path {0}", m_teamServerModulesDirectoryPath.c_str()); - std::size_t modulesLoaded = 0; - try - { - for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath)) - { - if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".so") - { - m_logger->debug("Trying to load {0}", entry.path().c_str()); - - void* handle = dlopen(entry.path().c_str(), RTLD_LAZY); - - if (!handle) - { - m_logger->warn("Failed to load {0}", entry.path().c_str()); - continue; - } - - std::string funcName = entry.path().filename(); - funcName = funcName.substr(3); // remove lib - funcName = funcName.substr(0, funcName.length() - 3); // remove .so - funcName += "Constructor"; // add Constructor - - m_logger->debug("Looking for construtor function {0}", funcName); - - constructProc construct = (constructProc)dlsym(handle, funcName.c_str()); - if (construct == NULL) - { - m_logger->warn("Failed to find construtor"); - dlclose(handle); - continue; - } - - ModuleCmd* moduleCmd = construct(); - - std::unique_ptr moduleCmd_(moduleCmd); - m_moduleCmd.push_back(std::move(moduleCmd_)); - - runtimeConfig.configureModule(*m_moduleCmd.back()); - - m_logger->debug("Module {0} loaded", entry.path().filename().c_str()); - modulesLoaded++; - } - } - } - catch (const std::filesystem::filesystem_error& e) - { - m_logger->warn("Error accessing module directory"); - } + m_moduleCmd, + [this]() + { return m_moduleLoader->loadModules(); }); - if (modulesLoaded == 0) - m_logger->warn("No TeamServer modules loaded from {0}", m_teamServerModulesDirectoryPath.c_str()); - else - m_logger->info("Loaded {0} TeamServer module(s) from {1}", modulesLoaded, m_teamServerModulesDirectoryPath.c_str()); + m_moduleCmd = m_moduleLoader->loadModules(); m_handleCmdResponseThreadRuning = true; m_handleCmdResponseThread = std::make_unique(&TeamServer::handleCmdResponse, this); @@ -278,7 +232,7 @@ void static inline splitInputCmd(const std::string& input, std::vectorcanHandle(instruction)) { - m_logger->debug("infoListener {0}", cmd); - - if (splitedCmd.size() == 2) - { - std::string listenerHash = splitedCmd[1]; - - for (int i = 0; i < m_listeners.size(); i++) - { - const std::string& hash = m_listeners[i]->getListenerHash(); - - // Check if the hash of the primary listener start with the given hash: - if (hash.rfind(listenerHash, 0) == 0) - { - std::string type = m_listeners[i]->getType(); - - std::string domainName = ""; - auto it = m_config.find("DomainName"); - if (it != m_config.end()) - domainName = m_config["DomainName"].get(); - - std::string exposedIp = ""; - it = m_config.find("ExposedIp"); - if (it != m_config.end()) - exposedIp = m_config["ExposedIp"].get(); - - std::string interface = ""; - it = m_config.find("IpInterface"); - if (it != m_config.end()) - interface = m_config["IpInterface"].get(); - - std::string ip = ""; - if (!interface.empty()) - ip = getIPAddress(interface); - - if (ip.empty() && domainName.empty() && exposedIp.empty()) - { - responseTmp.set_result("Error: No IP or Hostname in config."); - *response = responseTmp; - return grpc::Status::OK; - } - - std::string port = m_listeners[i]->getParam2(); - std::string uriFileDownload = ""; - - if (type == ListenerHttpType) - { - json configHttp = m_config["ListenerHttpConfig"]; - - auto it = configHttp.find("uriFileDownload"); - if (it != configHttp.end()) - uriFileDownload = configHttp["uriFileDownload"].get(); - } - else if (type == ListenerHttpsType) - { - json configHttps = m_config["ListenerHttpsConfig"]; - - auto it = configHttps.find("uriFileDownload"); - if (it != configHttps.end()) - uriFileDownload = configHttps["uriFileDownload"].get(); - ; - } - - std::string finalDomain; - if (!domainName.empty()) - finalDomain = domainName; - else if (!exposedIp.empty()) - finalDomain = exposedIp; - else if (!ip.empty()) - finalDomain = ip; - - m_logger->debug("infoListener found in primary listeners {0} {1} {2}", type, finalDomain, port); - - std::string result = type; - result += "\n"; - result += finalDomain; - result += "\n"; - result += port; - result += "\n"; - result += uriFileDownload; - - responseTmp.set_result(result); - } - // Check secondary listeners - smb / tcp: - else - { - // check for each sessions alive from this listener check if their is listeners - int nbSession = m_listeners[i]->getNumberOfSession(); - for (int kk = 0; kk < nbSession; kk++) - { - std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - - if (!session->isSessionKilled()) - { - for (auto it = session->getListener().begin(); it != session->getListener().end(); ++it) - { - - const std::string& hash = it->getListenerHash(); - - // Check if the hash of the primary listener start with the given hash: - if (hash.rfind(listenerHash, 0) == 0) - { - // TODO we got an issue here to get the ip where the the listener can be contacted ? especialy for smb ? - std::string type = it->getType(); - std::string param1 = it->getParam1(); - std::string param2 = it->getParam2(); - - m_logger->debug("infoListener found in beacon listener {0} {1} {2}", type, param1, param2); - - std::string result = type; - result += "\n"; - result += param1; - result += "\n"; - result += param2; - result += "\n"; - result += "none"; - - responseTmp.set_result(result); - } - } - } - } - } - } - - if (responseTmp.result().empty()) - { - m_logger->error("Error: Listener {} not found.", listenerHash); - - responseTmp.set_result("Error: Listener not found."); - *response = responseTmp; - return grpc::Status::OK; - } - } - else - { - responseTmp.set_result("Error: infoListener take one arguement."); - *response = responseTmp; - return grpc::Status::OK; - } - } - else if (instruction == GetBeaconBinaryInstruction) - { - m_logger->debug("getBeaconBinary {0}", cmd); - - if (splitedCmd.size() == 2 || splitedCmd.size() == 3) - { - std::string listenerHash = splitedCmd[1]; - - std::string targetOs = "Windows"; - if (splitedCmd.size() == 3 && splitedCmd[2] == "Linux") - targetOs = "Linux"; - - for (int i = 0; i < m_listeners.size(); i++) - { - const std::string& hash = m_listeners[i]->getListenerHash(); - - // Check if the hash of the primary listener start with the given hash: - if (hash.rfind(listenerHash, 0) == 0) - { - std::string type = m_listeners[i]->getType(); - std::string beaconFilePath = ""; - if (type == ListenerHttpType || type == ListenerHttpsType) - { - if (targetOs == "Linux") - { - beaconFilePath = m_linuxBeaconsDirectoryPath; - beaconFilePath += "BeaconHttp"; - } - else - { - beaconFilePath = m_windowsBeaconsDirectoryPath; - beaconFilePath += "BeaconHttp.exe"; - } - } - else if (type == ListenerTcpType) - { - if (targetOs == "Linux") - { - beaconFilePath = m_linuxBeaconsDirectoryPath; - beaconFilePath += "BeaconTcp"; - } - else - { - beaconFilePath = m_windowsBeaconsDirectoryPath; - beaconFilePath += "BeaconTcp.exe"; - } - } - else if (type == ListenerGithubType) - { - if (targetOs == "Linux") - { - beaconFilePath = m_linuxBeaconsDirectoryPath; - beaconFilePath += "BeaconGithub"; - } - else - { - beaconFilePath = m_windowsBeaconsDirectoryPath; - beaconFilePath += "BeaconGithub.exe"; - } - } - else if (type == ListenerDnsType) - { - if (targetOs == "Linux") - { - beaconFilePath = m_linuxBeaconsDirectoryPath; - beaconFilePath += "BeaconDns"; - } - else - { - beaconFilePath = m_windowsBeaconsDirectoryPath; - beaconFilePath += "BeaconDns.exe"; - } - } - - std::ifstream beaconFile(beaconFilePath, std::ios::binary); - if (beaconFile.good()) - { - m_logger->info("getBeaconBinary found in primary listeners {0} {1}", type, targetOs); - - std::string binaryData((std::istreambuf_iterator(beaconFile)), std::istreambuf_iterator()); - responseTmp.set_data(binaryData); - responseTmp.set_result("ok"); - } - else - { - m_logger->error("Error: Beacons {0} {1} not found.", type, targetOs); - - responseTmp.set_result("Error: Beacons not found."); - *response = responseTmp; - return grpc::Status::OK; - } - } - // Check secondary listeners - smb / tcp: - else - { - // check for each sessions alive from this listener check if their is listeners - int nbSession = m_listeners[i]->getNumberOfSession(); - for (int kk = 0; kk < nbSession; kk++) - { - std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - - if (!session->isSessionKilled()) - { - for (auto it = session->getListener().begin(); it != session->getListener().end(); ++it) - { - const std::string& hash = it->getListenerHash(); - - // Check if the hash of the primary listener start with the given hash: - if (hash.rfind(listenerHash, 0) == 0) - { - std::string type = it->getType(); - std::string param1 = it->getParam1(); - std::string param2 = it->getParam2(); - - std::string beaconFilePath = ""; - if (type == ListenerTcpType) - { - if (targetOs == "Linux") - { - beaconFilePath = m_linuxBeaconsDirectoryPath; - beaconFilePath += "BeaconTcp"; - } - else - { - beaconFilePath = m_windowsBeaconsDirectoryPath; - beaconFilePath += "BeaconTcp.exe"; - } - } - else if (type == ListenerSmbType) - { - if (targetOs == "Linux") - { - beaconFilePath = m_linuxBeaconsDirectoryPath; - beaconFilePath += "BeaconSmb"; - } - else - { - beaconFilePath = m_windowsBeaconsDirectoryPath; - beaconFilePath += "BeaconSmb.exe"; - } - } - - std::ifstream beaconFile(beaconFilePath, std::ios::binary); - if (beaconFile.good()) - { - m_logger->info("getBeaconBinary found in beacon listeners {0} {1}", type, targetOs); - - std::string binaryData((std::istreambuf_iterator(beaconFile)), std::istreambuf_iterator()); - responseTmp.set_data(binaryData); - responseTmp.set_result("ok"); - } - else - { - m_logger->error("Error: Beacons {0} {1} not found.", type, targetOs); - - responseTmp.set_result("Error: Beacons not found."); - *response = responseTmp; - return grpc::Status::OK; - } - } - } - } - } - } - } - - if (responseTmp.result().empty()) - { - responseTmp.set_result("Error: Listener not found."); - *response = responseTmp; - return grpc::Status::OK; - } - } - else - { - responseTmp.set_result("Error: getBeaconBinary take one arguement."); - *response = responseTmp; - return grpc::Status::OK; - } + return m_listenerArtifactService->handleCommand(instruction, splitedCmd, *command, response); } else if (m_termLocalService->canHandle(instruction)) { @@ -676,121 +309,7 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser return grpc::Status::OK; } -std::string toLower(const std::string& str) -{ - std::string result = str; - std::transform(result.begin(), result.end(), result.begin(), - [](unsigned char c) - { return std::tolower(c); }); - return result; -} - int TeamServer::prepMsg(const std::string& input, C2Message& c2Message, bool isWindows) { - m_logger->trace("prepMsg"); - - std::vector splitedCmd; - splitInputCmd(input, splitedCmd); - - if (splitedCmd.empty()) - return 0; - - int res = 0; - string instruction = splitedCmd[0]; - bool isModuleFound = false; - for (int i = 0; i < m_commonCommands.getNumberOfCommand(); i++) - { - if (instruction == m_commonCommands.getCommand(i)) - { - // check the path / file name / instruction given for translation - if (instruction == LoadModuleInstruction) - { - if (splitedCmd.size() == 2) - { - std::string param = splitedCmd[1]; - - // Handle the 4 historicals commands where the cmd name don't match the module file name - if (param == "ls") - param = "listDirectory"; - else if (param == "cd") - param = "changeDirectory"; - else if (param == "ps") - param = "listProcesses"; - else if (param == "pwd") - param = "printWorkingDirectory"; - - if (param.size() >= 3 && param.substr(param.size() - 3) == ".so") - { - } - else if (param.size() >= 4 && param.substr(param.size() - 3) == ".dll") - { - } - else - { - m_logger->debug("Translate instruction to module name to load in {0}", m_teamServerModulesDirectoryPath.c_str()); - try - { - for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath)) - { - if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".so") - { - - std::string moduleName = entry.path().filename(); - moduleName = moduleName.substr(3); // remove lib - moduleName = moduleName.substr(0, moduleName.length() - 3); // remove .so - - if (toLower(param) == toLower(moduleName)) - { - if (isWindows) - { - splitedCmd[1] = moduleName; - splitedCmd[1] += ".dll"; - } - else - { - splitedCmd[1] = entry.path().filename(); - } - - m_logger->debug("Found module to load {0}", splitedCmd[1]); - } - } - } - } - catch (const std::filesystem::filesystem_error& e) - { - m_logger->warn("Error accessing module directory"); - } - } - } - } - res = m_commonCommands.init(splitedCmd, c2Message, isWindows); - isModuleFound = true; - } - } - - for (auto it = m_moduleCmd.begin(); it != m_moduleCmd.end(); ++it) - { - if (toLower(instruction) == toLower((*it)->getName())) - { - splitedCmd[0] = (*it)->getName(); - res = (*it)->init(splitedCmd, c2Message); - isModuleFound = true; - } - } - - if (!isModuleFound) - { - m_logger->warn("Module {0} not found.", instruction); - - std::string hint = "Module "; - hint += instruction; - hint += " not found."; - c2Message.set_returnvalue(hint); - - res = -1; - } - - m_logger->trace("prepMsg end"); - - return res; + return m_commandPreparationService->prepareMessage(input, c2Message, isWindows); } diff --git a/teamServer/teamServer/TeamServer.hpp b/teamServer/teamServer/TeamServer.hpp index 7618cf0..c284da7 100644 --- a/teamServer/teamServer/TeamServer.hpp +++ b/teamServer/teamServer/TeamServer.hpp @@ -31,7 +31,10 @@ class TeamServerAuthManager; class TeamServerHelpService; class TeamServerListenerSessionService; +class TeamServerListenerArtifactService; +class TeamServerModuleLoader; class TeamServerSocksService; +class TeamServerCommandPreparationService; class TeamServerTermLocalService; class TeamServer final : public teamserverapi::TeamServerApi::Service @@ -74,14 +77,6 @@ class TeamServer final : public teamserverapi::TeamServerApi::Service std::vector> m_moduleCmd; CommonCommands m_commonCommands; - std::string m_teamServerModulesDirectoryPath; - std::string m_linuxModulesDirectoryPath; - std::string m_windowsModulesDirectoryPath; - std::string m_linuxBeaconsDirectoryPath; - std::string m_windowsBeaconsDirectoryPath; - std::string m_toolsDirectoryPath; - std::string m_scriptsDirectoryPath; - bool m_handleCmdResponseThreadRuning; std::unique_ptr m_handleCmdResponseThread; std::vector m_cmdResponses; @@ -92,6 +87,9 @@ class TeamServer final : public teamserverapi::TeamServerApi::Service std::unique_ptr m_authManager; std::unique_ptr m_helpService; std::unique_ptr m_listenerSessionService; + std::unique_ptr m_listenerArtifactService; + std::unique_ptr m_moduleLoader; std::unique_ptr m_socksService; + std::unique_ptr m_commandPreparationService; std::unique_ptr m_termLocalService; }; diff --git a/teamServer/teamServer/TeamServerCommandPreparationService.cpp b/teamServer/teamServer/TeamServerCommandPreparationService.cpp new file mode 100644 index 0000000..01877e6 --- /dev/null +++ b/teamServer/teamServer/TeamServerCommandPreparationService.cpp @@ -0,0 +1,143 @@ +#include "TeamServerCommandPreparationService.hpp" + +#include +#include +#include + +namespace fs = std::filesystem; + +TeamServerCommandPreparationService::TeamServerCommandPreparationService( + std::shared_ptr logger, + std::string teamServerModulesDirectoryPath, + CommonCommands& commonCommands, + std::vector>& moduleCmd) + : m_logger(std::move(logger)), + m_teamServerModulesDirectoryPath(std::move(teamServerModulesDirectoryPath)), + m_commonCommands(commonCommands), + m_moduleCmd(moduleCmd) +{ +} + +void TeamServerCommandPreparationService::splitInputCmd(const std::string& input, std::vector& splitedList) const +{ + std::string tmp; + for (size_t i = 0; i < input.size(); i++) + { + const char c = input[i]; + if (c == ' ') + { + if (!tmp.empty()) + splitedList.push_back(tmp); + tmp.clear(); + } + else if (c == '\'') + { + i++; + while (input[i] != '\'') + { + tmp += input[i]; + i++; + } + } + else + { + tmp += c; + } + } + + if (!tmp.empty()) + splitedList.push_back(tmp); +} + +std::string TeamServerCommandPreparationService::toLower(const std::string& str) +{ + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) + { return static_cast(std::tolower(c)); }); + return result; +} + +int TeamServerCommandPreparationService::prepareMessage(const std::string& input, C2Message& c2Message, bool isWindows) const +{ + m_logger->trace("prepMsg"); + + std::vector splitedCmd; + splitInputCmd(input, splitedCmd); + if (splitedCmd.empty()) + return 0; + + int res = 0; + const std::string instruction = splitedCmd[0]; + bool isModuleFound = false; + + for (int i = 0; i < m_commonCommands.getNumberOfCommand(); i++) + { + if (instruction != m_commonCommands.getCommand(i)) + continue; + + if (instruction == LoadModuleInstruction && splitedCmd.size() == 2) + { + std::string param = splitedCmd[1]; + if (param == "ls") + param = "listDirectory"; + else if (param == "cd") + param = "changeDirectory"; + else if (param == "ps") + param = "listProcesses"; + else if (param == "pwd") + param = "printWorkingDirectory"; + + if (!((param.size() >= 3 && param.substr(param.size() - 3) == ".so") + || (param.size() >= 4 && param.substr(param.size() - 3) == ".dll"))) + { + m_logger->debug("Translate instruction to module name to load in {0}", m_teamServerModulesDirectoryPath.c_str()); + try + { + for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath)) + { + if (!fs::is_regular_file(entry.path()) || entry.path().extension() != ".so") + continue; + + std::string moduleName = entry.path().filename(); + moduleName = moduleName.substr(3); + moduleName = moduleName.substr(0, moduleName.length() - 3); + + if (toLower(param) == toLower(moduleName)) + { + splitedCmd[1] = isWindows ? moduleName + ".dll" : entry.path().filename().string(); + m_logger->debug("Found module to load {0}", splitedCmd[1]); + } + } + } + catch (const fs::filesystem_error&) + { + m_logger->warn("Error accessing module directory"); + } + } + } + + res = m_commonCommands.init(splitedCmd, c2Message, isWindows); + isModuleFound = true; + } + + for (auto it = m_moduleCmd.begin(); it != m_moduleCmd.end(); ++it) + { + if (toLower(instruction) != toLower((*it)->getName())) + continue; + + splitedCmd[0] = (*it)->getName(); + res = (*it)->init(splitedCmd, c2Message); + isModuleFound = true; + } + + if (!isModuleFound) + { + m_logger->warn("Module {0} not found.", instruction); + c2Message.set_returnvalue("Module " + instruction + " not found."); + res = -1; + } + + m_logger->trace("prepMsg end"); + return res; +} diff --git a/teamServer/teamServer/TeamServerCommandPreparationService.hpp b/teamServer/teamServer/TeamServerCommandPreparationService.hpp new file mode 100644 index 0000000..3926121 --- /dev/null +++ b/teamServer/teamServer/TeamServerCommandPreparationService.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +#include "modules/ModuleCmd/CommonCommand.hpp" +#include "modules/ModuleCmd/ModuleCmd.hpp" +#include "spdlog/logger.h" + +class TeamServerCommandPreparationService +{ +public: + TeamServerCommandPreparationService( + std::shared_ptr logger, + std::string teamServerModulesDirectoryPath, + CommonCommands& commonCommands, + std::vector>& moduleCmd); + + int prepareMessage(const std::string& input, C2Message& c2Message, bool isWindows = true) const; + +private: + static std::string toLower(const std::string& str); + void splitInputCmd(const std::string& input, std::vector& splitedList) const; + + std::shared_ptr m_logger; + std::string m_teamServerModulesDirectoryPath; + CommonCommands& m_commonCommands; + std::vector>& m_moduleCmd; +}; diff --git a/teamServer/teamServer/TeamServerListenerArtifactService.cpp b/teamServer/teamServer/TeamServerListenerArtifactService.cpp new file mode 100644 index 0000000..bd98cbc --- /dev/null +++ b/teamServer/teamServer/TeamServerListenerArtifactService.cpp @@ -0,0 +1,259 @@ +#include "TeamServerListenerArtifactService.hpp" + +#include + +#include "listener/ListenerHttp.hpp" +#include "modules/ModuleCmd/CommonCommand.hpp" + +namespace +{ +const std::string InfoListenerInstruction = "infoListener"; +const std::string GetBeaconBinaryInstruction = "getBeaconBinary"; + +std::string readBinaryFile(const std::string& path) +{ + std::ifstream input(path, std::ios::binary); + return std::string((std::istreambuf_iterator(input)), std::istreambuf_iterator()); +} +} // namespace + +TeamServerListenerArtifactService::TeamServerListenerArtifactService( + std::shared_ptr logger, + const nlohmann::json& config, + TeamServerRuntimeConfig runtimeConfig, + std::vector>& listeners, + IpResolver ipResolver) + : m_logger(std::move(logger)), + m_config(config), + m_runtimeConfig(std::move(runtimeConfig)), + m_listeners(listeners), + m_ipResolver(std::move(ipResolver)) +{ +} + +bool TeamServerListenerArtifactService::canHandle(const std::string& instruction) const +{ + return instruction == InfoListenerInstruction || instruction == GetBeaconBinaryInstruction; +} + +grpc::Status TeamServerListenerArtifactService::handleCommand( + const std::string& instruction, + const std::vector& splitedCmd, + const teamserverapi::TermCommand& command, + teamserverapi::TermCommand* response) const +{ + response->set_cmd(""); + response->set_result(""); + response->set_data(""); + + if (instruction == InfoListenerInstruction) + return handleInfoListener(splitedCmd, command.cmd(), response); + if (instruction == GetBeaconBinaryInstruction) + return handleGetBeaconBinary(splitedCmd, command.cmd(), response); + + response->set_result("Error: not implemented."); + return grpc::Status::OK; +} + +std::string TeamServerListenerArtifactService::resolvePublicAddress() const +{ + const auto domainIt = m_config.find("DomainName"); + if (domainIt != m_config.end()) + return domainIt->get(); + + const auto exposedIt = m_config.find("ExposedIp"); + if (exposedIt != m_config.end()) + return exposedIt->get(); + + const auto interfaceIt = m_config.find("IpInterface"); + if (interfaceIt != m_config.end() && !interfaceIt->get().empty() && m_ipResolver) + return m_ipResolver(interfaceIt->get()); + + return ""; +} + +std::string TeamServerListenerArtifactService::resolvePrimaryListenerInfo(const std::shared_ptr& listener) const +{ + const std::string finalAddress = resolvePublicAddress(); + if (finalAddress.empty()) + return ""; + + std::string uriFileDownload; + const std::string type = listener->getType(); + if (type == ListenerHttpType) + { + const auto configHttp = m_config["ListenerHttpConfig"]; + const auto it = configHttp.find("uriFileDownload"); + if (it != configHttp.end()) + uriFileDownload = configHttp["uriFileDownload"].get(); + } + else if (type == ListenerHttpsType) + { + const auto configHttps = m_config["ListenerHttpsConfig"]; + const auto it = configHttps.find("uriFileDownload"); + if (it != configHttps.end()) + uriFileDownload = configHttps["uriFileDownload"].get(); + } + + std::string result = type; + result += "\n"; + result += finalAddress; + result += "\n"; + result += listener->getParam2(); + result += "\n"; + result += uriFileDownload; + return result; +} + +std::string TeamServerListenerArtifactService::resolveBeaconBinaryPath( + const std::string& type, + const std::string& targetOs, + bool primaryListener) const +{ + const bool linuxTarget = targetOs == "Linux"; + if (type == ListenerHttpType || type == ListenerHttpsType) + return linuxTarget ? m_runtimeConfig.linuxBeaconsDirectoryPath + "BeaconHttp" : m_runtimeConfig.windowsBeaconsDirectoryPath + "BeaconHttp.exe"; + if (type == ListenerTcpType) + return linuxTarget ? m_runtimeConfig.linuxBeaconsDirectoryPath + "BeaconTcp" : m_runtimeConfig.windowsBeaconsDirectoryPath + "BeaconTcp.exe"; + if (primaryListener && type == ListenerGithubType) + return linuxTarget ? m_runtimeConfig.linuxBeaconsDirectoryPath + "BeaconGithub" : m_runtimeConfig.windowsBeaconsDirectoryPath + "BeaconGithub.exe"; + if (primaryListener && type == ListenerDnsType) + return linuxTarget ? m_runtimeConfig.linuxBeaconsDirectoryPath + "BeaconDns" : m_runtimeConfig.windowsBeaconsDirectoryPath + "BeaconDns.exe"; + if (!primaryListener && type == ListenerSmbType) + return linuxTarget ? m_runtimeConfig.linuxBeaconsDirectoryPath + "BeaconSmb" : m_runtimeConfig.windowsBeaconsDirectoryPath + "BeaconSmb.exe"; + return ""; +} + +grpc::Status TeamServerListenerArtifactService::handleInfoListener( + const std::vector& splitedCmd, + const std::string& cmd, + teamserverapi::TermCommand* response) const +{ + m_logger->debug("infoListener {0}", cmd); + + if (splitedCmd.size() != 2) + { + response->set_result("Error: infoListener take one arguement."); + return grpc::Status::OK; + } + + const std::string& listenerHash = splitedCmd[1]; + for (const auto& listener : m_listeners) + { + const std::string& hash = listener->getListenerHash(); + if (hash.rfind(listenerHash, 0) == 0) + { + const std::string result = resolvePrimaryListenerInfo(listener); + if (result.empty()) + { + response->set_result("Error: No IP or Hostname in config."); + return grpc::Status::OK; + } + + m_logger->debug("infoListener found in primary listeners {0}", hash); + response->set_result(result); + return grpc::Status::OK; + } + + const int nbSession = listener->getNumberOfSession(); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = listener->getSessionPtr(kk); + if (session->isSessionKilled()) + continue; + + for (auto it = session->getListener().begin(); it != session->getListener().end(); ++it) + { + const std::string& secondaryHash = it->getListenerHash(); + if (secondaryHash.rfind(listenerHash, 0) != 0) + continue; + + std::string result = it->getType(); + result += "\n"; + result += it->getParam1(); + result += "\n"; + result += it->getParam2(); + result += "\n"; + result += "none"; + + m_logger->debug("infoListener found in beacon listener {0} {1} {2}", it->getType(), it->getParam1(), it->getParam2()); + response->set_result(result); + return grpc::Status::OK; + } + } + } + + m_logger->error("Error: Listener {} not found.", listenerHash); + response->set_result("Error: Listener not found."); + return grpc::Status::OK; +} + +grpc::Status TeamServerListenerArtifactService::handleGetBeaconBinary( + const std::vector& splitedCmd, + const std::string& cmd, + teamserverapi::TermCommand* response) const +{ + m_logger->debug("getBeaconBinary {0}", cmd); + + if (splitedCmd.size() != 2 && splitedCmd.size() != 3) + { + response->set_result("Error: getBeaconBinary take one arguement."); + return grpc::Status::OK; + } + + const std::string& listenerHash = splitedCmd[1]; + const std::string targetOs = (splitedCmd.size() == 3 && splitedCmd[2] == "Linux") ? "Linux" : "Windows"; + + for (const auto& listener : m_listeners) + { + const std::string& hash = listener->getListenerHash(); + if (hash.rfind(listenerHash, 0) == 0) + { + const std::string beaconFilePath = resolveBeaconBinaryPath(listener->getType(), targetOs, true); + std::ifstream beaconFile(beaconFilePath, std::ios::binary); + if (!beaconFile.good()) + { + m_logger->error("Error: Beacons {0} {1} not found.", listener->getType(), targetOs); + response->set_result("Error: Beacons not found."); + return grpc::Status::OK; + } + + m_logger->info("getBeaconBinary found in primary listeners {0} {1}", listener->getType(), targetOs); + response->set_data(readBinaryFile(beaconFilePath)); + response->set_result("ok"); + return grpc::Status::OK; + } + + const int nbSession = listener->getNumberOfSession(); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = listener->getSessionPtr(kk); + if (session->isSessionKilled()) + continue; + + for (auto it = session->getListener().begin(); it != session->getListener().end(); ++it) + { + const std::string& secondaryHash = it->getListenerHash(); + if (secondaryHash.rfind(listenerHash, 0) != 0) + continue; + + const std::string beaconFilePath = resolveBeaconBinaryPath(it->getType(), targetOs, false); + std::ifstream beaconFile(beaconFilePath, std::ios::binary); + if (!beaconFile.good()) + { + m_logger->error("Error: Beacons {0} {1} not found.", it->getType(), targetOs); + response->set_result("Error: Beacons not found."); + return grpc::Status::OK; + } + + m_logger->info("getBeaconBinary found in beacon listeners {0} {1}", it->getType(), targetOs); + response->set_data(readBinaryFile(beaconFilePath)); + response->set_result("ok"); + return grpc::Status::OK; + } + } + } + + response->set_result("Error: Listener not found."); + return grpc::Status::OK; +} diff --git a/teamServer/teamServer/TeamServerListenerArtifactService.hpp b/teamServer/teamServer/TeamServerListenerArtifactService.hpp new file mode 100644 index 0000000..b91e8eb --- /dev/null +++ b/teamServer/teamServer/TeamServerListenerArtifactService.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include "TeamServerApi.pb.h" +#include "TeamServerRuntimeConfig.hpp" +#include "listener/Listener.hpp" +#include "nlohmann/json.hpp" +#include "spdlog/logger.h" + +class TeamServerListenerArtifactService +{ +public: + using IpResolver = std::function; + + TeamServerListenerArtifactService( + std::shared_ptr logger, + const nlohmann::json& config, + TeamServerRuntimeConfig runtimeConfig, + std::vector>& listeners, + IpResolver ipResolver = {}); + + bool canHandle(const std::string& instruction) const; + grpc::Status handleCommand( + const std::string& instruction, + const std::vector& splitedCmd, + const teamserverapi::TermCommand& command, + teamserverapi::TermCommand* response) const; + +private: + std::string resolvePublicAddress() const; + std::string resolvePrimaryListenerInfo(const std::shared_ptr& listener) const; + std::string resolveBeaconBinaryPath(const std::string& type, const std::string& targetOs, bool primaryListener) const; + grpc::Status handleInfoListener( + const std::vector& splitedCmd, + const std::string& cmd, + teamserverapi::TermCommand* response) const; + grpc::Status handleGetBeaconBinary( + const std::vector& splitedCmd, + const std::string& cmd, + teamserverapi::TermCommand* response) const; + + std::shared_ptr m_logger; + const nlohmann::json& m_config; + TeamServerRuntimeConfig m_runtimeConfig; + std::vector>& m_listeners; + IpResolver m_ipResolver; +}; diff --git a/teamServer/teamServer/TeamServerModuleLoader.cpp b/teamServer/teamServer/TeamServerModuleLoader.cpp new file mode 100644 index 0000000..c292ea9 --- /dev/null +++ b/teamServer/teamServer/TeamServerModuleLoader.cpp @@ -0,0 +1,84 @@ +#include "TeamServerModuleLoader.hpp" + +#include + +#include + +namespace fs = std::filesystem; + +namespace +{ +using constructProc = ModuleCmd* (*)(); +} + +TeamServerModuleLoader::TeamServerModuleLoader( + std::shared_ptr logger, + TeamServerRuntimeConfig runtimeConfig) + : m_logger(std::move(logger)), + m_runtimeConfig(std::move(runtimeConfig)) +{ +} + +std::vector> TeamServerModuleLoader::loadModules() const +{ + std::vector> modules; + + m_logger->debug("TeamServer module directory path {0}", m_runtimeConfig.teamServerModulesDirectoryPath.c_str()); + + try + { + for (const auto& entry : fs::recursive_directory_iterator(m_runtimeConfig.teamServerModulesDirectoryPath)) + { + if (!fs::is_regular_file(entry.path()) || entry.path().extension() != ".so") + continue; + + m_logger->debug("Trying to load {0}", entry.path().c_str()); + + void* handle = dlopen(entry.path().c_str(), RTLD_LAZY); + if (!handle) + { + m_logger->warn("Failed to load {0}: {1}", entry.path().c_str(), dlerror()); + continue; + } + + std::string funcName = entry.path().filename(); + funcName = funcName.substr(3); + funcName = funcName.substr(0, funcName.length() - 3); + funcName += "Constructor"; + + m_logger->debug("Looking for constructor function {0}", funcName); + + constructProc construct = reinterpret_cast(dlsym(handle, funcName.c_str())); + if (!construct) + { + m_logger->warn("Failed to find constructor: {0}", dlerror()); + dlclose(handle); + continue; + } + + ModuleCmd* moduleCmd = construct(); + if (!moduleCmd) + { + m_logger->warn("Constructor returned null"); + dlclose(handle); + continue; + } + + std::unique_ptr moduleCmdPtr(moduleCmd); + m_runtimeConfig.configureModule(*moduleCmdPtr); + m_logger->debug("Module {0} loaded", entry.path().filename().c_str()); + modules.push_back(std::move(moduleCmdPtr)); + } + } + catch (const fs::filesystem_error&) + { + m_logger->warn("Error accessing module directory"); + } + + if (modules.empty()) + m_logger->warn("No TeamServer modules loaded from {0}", m_runtimeConfig.teamServerModulesDirectoryPath.c_str()); + else + m_logger->info("Loaded {0} TeamServer module(s) from {1}", modules.size(), m_runtimeConfig.teamServerModulesDirectoryPath.c_str()); + + return modules; +} diff --git a/teamServer/teamServer/TeamServerModuleLoader.hpp b/teamServer/teamServer/TeamServerModuleLoader.hpp new file mode 100644 index 0000000..2854470 --- /dev/null +++ b/teamServer/teamServer/TeamServerModuleLoader.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include "TeamServerRuntimeConfig.hpp" +#include "modules/ModuleCmd/ModuleCmd.hpp" +#include "spdlog/logger.h" + +class TeamServerModuleLoader +{ +public: + TeamServerModuleLoader( + std::shared_ptr logger, + TeamServerRuntimeConfig runtimeConfig); + + std::vector> loadModules() const; + +private: + std::shared_ptr m_logger; + TeamServerRuntimeConfig m_runtimeConfig; +}; diff --git a/teamServer/teamServer/TeamServerTermLocalService.cpp b/teamServer/teamServer/TeamServerTermLocalService.cpp index b9513c2..d5a382a 100644 --- a/teamServer/teamServer/TeamServerTermLocalService.cpp +++ b/teamServer/teamServer/TeamServerTermLocalService.cpp @@ -1,19 +1,13 @@ #include "TeamServerTermLocalService.hpp" -#include - -#include #include +#include "TeamServerModuleLoader.hpp" #include "listener/ListenerHttp.hpp" - -namespace fs = std::filesystem; using json = nlohmann::json; namespace { -using constructProc = ModuleCmd* (*)(); - const std::string PutIntoUploadDirInstruction = "putIntoUploadDir"; const std::string ReloadModulesInstruction = "reloadModules"; const std::string BatcaveInstruction = "batcaveUpload"; @@ -75,59 +69,8 @@ grpc::Status TeamServerTermLocalService::handleCommand( std::vector> TeamServerTermLocalService::loadModulesFromDisk() const { - std::vector> modules; - - try - { - for (const auto& entry : fs::recursive_directory_iterator(m_runtimeConfig.teamServerModulesDirectoryPath)) - { - if (!fs::is_regular_file(entry.path()) || entry.path().extension() != ".so") - continue; - - m_logger->debug("Trying to load {0}", entry.path().c_str()); - - void* handle = dlopen(entry.path().c_str(), RTLD_LAZY); - if (!handle) - { - m_logger->warn("Failed to load {0}: {1}", entry.path().c_str(), dlerror()); - continue; - } - - std::string funcName = entry.path().filename(); - funcName = funcName.substr(3); - funcName = funcName.substr(0, funcName.length() - 3); - funcName += "Constructor"; - - m_logger->debug("Looking for constructor function: {0}", funcName); - - constructProc construct = reinterpret_cast(dlsym(handle, funcName.c_str())); - if (!construct) - { - m_logger->warn("Failed to find constructor: {0}", dlerror()); - dlclose(handle); - continue; - } - - ModuleCmd* moduleCmd = construct(); - if (!moduleCmd) - { - m_logger->warn("Constructor returned null"); - dlclose(handle); - continue; - } - - std::unique_ptr moduleCmdPtr(moduleCmd); - m_runtimeConfig.configureModule(*moduleCmdPtr); - m_logger->debug("Module {0} loaded", entry.path().filename().c_str()); - modules.push_back(std::move(moduleCmdPtr)); - } - } - catch (const fs::filesystem_error& e) - { - m_logger->warn("Error accessing module directory: {0}", e.what()); - } - - return modules; + TeamServerModuleLoader loader(m_logger, m_runtimeConfig); + return loader.loadModules(); } bool TeamServerTermLocalService::isValidFilename(const std::string& filename) const diff --git a/teamServer/tests/TeamServerCommandPreparationServiceTests.cpp b/teamServer/tests/TeamServerCommandPreparationServiceTests.cpp new file mode 100644 index 0000000..01e5aeb --- /dev/null +++ b/teamServer/tests/TeamServerCommandPreparationServiceTests.cpp @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include + +#include "TeamServerCommandPreparationService.hpp" + +namespace fs = std::filesystem; + +namespace +{ +class ScopedPath +{ +public: + explicit ScopedPath(fs::path path) + : m_path(std::move(path)) + { + } + + ~ScopedPath() + { + std::error_code ec; + fs::remove_all(m_path, ec); + } + + const fs::path& path() const + { + return m_path; + } + +private: + fs::path m_path; +}; + +class FakeModule final : public ModuleCmd +{ +public: + explicit FakeModule(std::string name) + : ModuleCmd(std::move(name)) + { + } + + std::string getInfo() override + { + return "fake"; + } + + int init(std::vector&, C2Message& c2Message) override + { + c2Message.set_instruction("FAKE"); + return 42; + } + + int process(C2Message&, C2Message&) override + { + return 0; + } +}; + +fs::path makeTempDirectory(const std::string& name) +{ + fs::path root = fs::temp_directory_path() / ("c2teamserver-prep-" + name + "-" + std::to_string(::getpid())); + fs::create_directories(root); + return root; +} + +std::shared_ptr makeLogger() +{ + auto logger = std::make_shared("prep-tests"); + logger->set_level(spdlog::level::off); + return logger; +} + +void testPrepareCommonCommand() +{ + ScopedPath tempRoot(makeTempDirectory("common")); + CommonCommands commonCommands; + std::vector> modules; + TeamServerCommandPreparationService service( + makeLogger(), + tempRoot.path().string(), + commonCommands, + modules); + + C2Message message; + assert(service.prepareMessage("sleep 0.5", message, true) == 0); + assert(message.instruction() == SleepCmd); +} + +void testPrepareModuleCommandCaseInsensitive() +{ + ScopedPath tempRoot(makeTempDirectory("module")); + CommonCommands commonCommands; + std::vector> modules; + modules.push_back(std::make_unique("FakeModule")); + + TeamServerCommandPreparationService service( + makeLogger(), + tempRoot.path().string(), + commonCommands, + modules); + + C2Message message; + assert(service.prepareMessage("fakemodule anything", message, true) == 42); + assert(message.instruction() == "FAKE"); +} + +void testPrepareMissingCommand() +{ + ScopedPath tempRoot(makeTempDirectory("missing")); + CommonCommands commonCommands; + std::vector> modules; + + TeamServerCommandPreparationService service( + makeLogger(), + tempRoot.path().string(), + commonCommands, + modules); + + C2Message message; + assert(service.prepareMessage("doesnotexist", message, true) == -1); + assert(message.returnvalue() == "Module doesnotexist not found."); +} +} // namespace + +int main() +{ + testPrepareCommonCommand(); + testPrepareModuleCommandCaseInsensitive(); + testPrepareMissingCommand(); + return 0; +} diff --git a/teamServer/tests/TeamServerListenerArtifactServiceTests.cpp b/teamServer/tests/TeamServerListenerArtifactServiceTests.cpp new file mode 100644 index 0000000..533131c --- /dev/null +++ b/teamServer/tests/TeamServerListenerArtifactServiceTests.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include + +#include "TeamServerListenerArtifactService.hpp" + +namespace fs = std::filesystem; + +namespace +{ +class ScopedPath +{ +public: + explicit ScopedPath(fs::path path) + : m_path(std::move(path)) + { + } + + ~ScopedPath() + { + std::error_code ec; + fs::remove_all(m_path, ec); + } + + const fs::path& path() const + { + return m_path; + } + +private: + fs::path m_path; +}; + +class TestListener final : public Listener +{ +public: + TestListener(const std::string& hash, const std::string& type = ListenerHttpsType, const std::string& param1 = "127.0.0.1", const std::string& param2 = "8443") + : Listener(param1, param2, type) + { + m_listenerHash = hash; + } + + std::shared_ptr addSession(const std::string& listenerHash, const std::string& beaconHash, const std::string& os) + { + auto session = std::make_shared(listenerHash, beaconHash, "host", "user", "x64", "admin", os); + m_sessions.push_back(session); + return session; + } +}; + +fs::path makeTempDirectory(const std::string& name) +{ + fs::path root = fs::temp_directory_path() / ("c2teamserver-artifacts-" + name + "-" + std::to_string(::getpid())); + fs::create_directories(root); + return root; +} + +std::shared_ptr makeLogger() +{ + auto logger = std::make_shared("artifact-tests"); + logger->set_level(spdlog::level::off); + return logger; +} + +TeamServerRuntimeConfig makeRuntimeConfig(const fs::path& root) +{ + TeamServerRuntimeConfig runtimeConfig; + runtimeConfig.teamServerModulesDirectoryPath = (root / "modules").string(); + runtimeConfig.linuxModulesDirectoryPath = (root / "linux-modules").string(); + runtimeConfig.windowsModulesDirectoryPath = (root / "windows-modules").string(); + runtimeConfig.linuxBeaconsDirectoryPath = (root / "linux-beacons/").string(); + runtimeConfig.windowsBeaconsDirectoryPath = (root / "windows-beacons/").string(); + runtimeConfig.toolsDirectoryPath = (root / "tools").string(); + runtimeConfig.scriptsDirectoryPath = (root / "scripts").string(); + + fs::create_directories(runtimeConfig.teamServerModulesDirectoryPath); + fs::create_directories(runtimeConfig.linuxModulesDirectoryPath); + fs::create_directories(runtimeConfig.windowsModulesDirectoryPath); + fs::create_directories(runtimeConfig.linuxBeaconsDirectoryPath); + fs::create_directories(runtimeConfig.windowsBeaconsDirectoryPath); + fs::create_directories(runtimeConfig.toolsDirectoryPath); + fs::create_directories(runtimeConfig.scriptsDirectoryPath); + return runtimeConfig; +} + +void writeFile(const fs::path& path, const std::string& content) +{ + std::ofstream output(path, std::ios::binary); + output << content; +} + +void testInfoListenerForPrimaryAndSecondary() +{ + ScopedPath tempRoot(makeTempDirectory("info")); + TeamServerRuntimeConfig runtimeConfig = makeRuntimeConfig(tempRoot.path()); + nlohmann::json config = { + {"DomainName", "team.example"}, + {"ListenerHttpsConfig", {{"uriFileDownload", "/drop.bin"}}}}; + + auto primary = std::make_shared("listener-primary"); + auto secondarySession = primary->addSession("listener-primary", "ABCDEFGH12345678", "Windows"); + secondarySession->addListener("secondary-hash", ListenerTcpType, "10.0.0.1", "4455"); + std::vector> listeners = {primary}; + + TeamServerListenerArtifactService service(makeLogger(), config, runtimeConfig, listeners); + + teamserverapi::TermCommand response; + teamserverapi::TermCommand command; + command.set_cmd("infoListener listener-pri"); + assert(service.handleCommand("infoListener", {"infoListener", "listener-pri"}, command, &response).ok()); + assert(response.result() == "https\nteam.example\n8443\n/drop.bin"); + + command.set_cmd("infoListener secondary"); + assert(service.handleCommand("infoListener", {"infoListener", "secondary"}, command, &response).ok()); + assert(response.result() == "tcp\n10.0.0.1\n4455\nnone"); +} + +void testGetBeaconBinaryForPrimaryAndSecondary() +{ + ScopedPath tempRoot(makeTempDirectory("beacon")); + TeamServerRuntimeConfig runtimeConfig = makeRuntimeConfig(tempRoot.path()); + writeFile(fs::path(runtimeConfig.windowsBeaconsDirectoryPath) / "BeaconHttp.exe", "HTTPBIN"); + writeFile(fs::path(runtimeConfig.windowsBeaconsDirectoryPath) / "BeaconSmb.exe", "SMBBIN"); + + nlohmann::json config = nlohmann::json::object(); + auto primary = std::make_shared("listener-primary"); + auto secondarySession = primary->addSession("listener-primary", "ABCDEFGH12345678", "Windows"); + secondarySession->addListener("secondary-hash", ListenerSmbType, "namedpipe", "none"); + std::vector> listeners = {primary}; + + TeamServerListenerArtifactService service(makeLogger(), config, runtimeConfig, listeners); + + teamserverapi::TermCommand response; + teamserverapi::TermCommand command; + command.set_cmd("getBeaconBinary listener-pri"); + assert(service.handleCommand("getBeaconBinary", {"getBeaconBinary", "listener-pri"}, command, &response).ok()); + assert(response.result() == "ok"); + assert(response.data() == "HTTPBIN"); + + command.set_cmd("getBeaconBinary secondary"); + assert(service.handleCommand("getBeaconBinary", {"getBeaconBinary", "secondary"}, command, &response).ok()); + assert(response.result() == "ok"); + assert(response.data() == "SMBBIN"); +} +} // namespace + +int main() +{ + testInfoListenerForPrimaryAndSecondary(); + testGetBeaconBinaryForPrimaryAndSecondary(); + return 0; +} From 06db71ba17b8962f5eaec8f6f840b85e3a7f0904 Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:37:12 +0200 Subject: [PATCH 17/28] Harden --- .github/workflows/Release.yml | 18 ++++- .github/workflows/Tests.yml | 37 +++++------ AGENT.md | 14 +++- C2Client/requirements.txt | 2 +- Dockerfile | 30 +++++---- README.md | 119 +++++++++++++++++++--------------- packaging/assemble_release.py | 7 +- 7 files changed, 130 insertions(+), 97 deletions(-) diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 5503f5b..df9cc53 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -36,11 +36,11 @@ jobs: with: python-version: '3.12' - - name: Get Conan - uses: turtlebrowser/get-conan@v1.2 + - name: Install Conan + run: python -m pip install --upgrade pip "conan==2.24.0" - name: Create default profile - run: conan profile detect + run: conan profile detect --force - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DWITH_TESTS=OFF -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake @@ -52,6 +52,18 @@ jobs: run: | cmake --build ${{github.workspace}}/build --target stage_release_bundle + - name: Verify staged client protocol bundle + run: | + python - <<'PY' + import pathlib + import sys + + bundle_root = pathlib.Path("${{github.workspace}}/build/release-staging/Release/Client").resolve() + sys.path.insert(0, str(bundle_root)) + import c2client_protocol.TeamServerApi_pb2 # noqa: F401 + print(bundle_root) + PY + - name: Import implant releases run: | set -euo pipefail diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml index ea4e8b7..218e983 100644 --- a/.github/workflows/Tests.yml +++ b/.github/workflows/Tests.yml @@ -15,36 +15,33 @@ env: jobs: buildAndTest: - # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. - # You can convert this to a matrix build if you need cross-platform coverage. - # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' - name: Install Samba client dev headers run: | sudo apt-get update sudo apt-get install -y libsmbclient-dev - - # Update references - - name: Git Sumbodule Update - run: | - git submodule update --init # Needed to generate certificates - uses: ConorMacBride/install-package@v1 with: apt: golang-cfssl - - name: Get Conan - # You may pin to the exact commit or the version. - # uses: turtlebrowser/get-conan@c171f295f3f507360ee018736a6608731aa2109d - uses: turtlebrowser/get-conan@v1.2 + - name: Install Conan + run: python -m pip install --upgrade pip "conan==2.24.0" - name: Create default profile - run: conan profile detect + run: conan profile detect --force - name: Configure CMake for tests run: cmake -B ${{github.workspace}}/build -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake @@ -53,16 +50,10 @@ jobs: run: cmake --build ${{github.workspace}}/build -j 18 - name: Run unit tests - run: ctest --test-dir build - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' + run: ctest --test-dir build --output-on-failure - name: Install client test dependencies run: | - python -m pip install --upgrade pip python -m pip install -e "${{github.workspace}}/C2Client[test]" - name: Run client tests @@ -71,6 +62,12 @@ jobs: QT_QPA_PLATFORM: offscreen run: python -m pytest "${{github.workspace}}/C2Client/tests" -q + - name: Stage release bundle smoke test + run: | + cmake --build ${{github.workspace}}/build --target stage_release_bundle + test -x "${{github.workspace}}/build/release-staging/Release/TeamServer/TeamServer" + test -f "${{github.workspace}}/build/release-staging/Release/Client/c2client_protocol/TeamServerApi_pb2.py" + - name: Configure CMake like the release run: cmake -B ${{github.workspace}}/buildRelease -DWITH_TESTS=OFF -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake diff --git a/AGENT.md b/AGENT.md index 07e687f..b061091 100644 --- a/AGENT.md +++ b/AGENT.md @@ -16,6 +16,7 @@ From now on, the project must be compiled and tested during implementation work - Prefer a clean dedicated build directory such as `build-codex-scratch` for verification instead of reusing an unknown existing `build/` tree. - Use Conan for dependencies, CMake for configuration, and GNU Make with GCC for the build. - After code changes, run at least the project configure step, the build, and `ctest --output-on-failure` when feasible. +- If the change touches the Python client or generated protocol bindings, also run the client pytest suite with `C2_PROTOCOL_PYTHON_ROOT` pointing at the build tree. ## Validated Environment @@ -39,11 +40,22 @@ make ctest --output-on-failure ``` +Run the Python client tests with: + +```bash +cd /home/max/project/C2TeamServer/C2Client +python -m venv .venv +. .venv/bin/activate +pip install -e .[test] +C2_PROTOCOL_PYTHON_ROOT=/home/max/project/C2TeamServer/build-codex-scratch/generated/python_protocol pytest tests -q +``` + Notes: - The `README.md` example using `-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./conan_provider.cmake` was not reliable in the validated setup. - Use the absolute path to `conan_provider.cmake` shown above. -- The validated CTest registration currently executes `testsTestServer`. +- The validated root build currently executes `53` CTest tests and `5` Python client tests. +- The staged release bundle is produced with `cmake --build --target stage_release_bundle` under `/release-staging/Release`. ## Responsibilities diff --git a/C2Client/requirements.txt b/C2Client/requirements.txt index 9829abf..fac2b5d 100644 --- a/C2Client/requirements.txt +++ b/C2Client/requirements.txt @@ -2,7 +2,7 @@ pycryptodome==3.23.0 grpcio==1.74.0 PyQt6==6.7.0 pyqtdarktheme -protobuf==6.32.0 +protobuf==6.33.5 gitpython==3.1.45 requests==2.32.5 pwn==1.0 diff --git a/Dockerfile b/Dockerfile index afb8d4f..8af3014 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ LABEL org.opencontainers.image.source="https://github.com/maxDcb/C2TeamServer" ENV TEAMSERVER_HOME=/opt/teamserver WORKDIR ${TEAMSERVER_HOME} +ARG C2TEAMSERVER_RELEASE_URL="" # Install minimal dependencies RUN apt-get update \ @@ -18,13 +19,18 @@ RUN apt-get update \ tar \ && rm -rf /var/lib/apt/lists/* -# Download and extract the latest Release from GitHub -RUN wget -q $(wget -q -O - "https://api.github.com/repos/maxDcb/C2TeamServer/releases/latest" \ - | jq -r '.assets[] | select(.name=="Release.tar.gz").browser_download_url') \ - -O /tmp/Release.tar.gz \ - && mkdir -p ${TEAMSERVER_HOME}/Release \ - && tar xf /tmp/Release.tar.gz --strip-components=1 -C ${TEAMSERVER_HOME}/Release \ - && rm /tmp/Release.tar.gz +# Download and extract the latest Release from GitHub, or use an explicit URL. +RUN set -eux; \ + if [ -n "${C2TEAMSERVER_RELEASE_URL}" ]; then \ + release_url="${C2TEAMSERVER_RELEASE_URL}"; \ + else \ + release_url="$(wget -q -O - "https://api.github.com/repos/maxDcb/C2TeamServer/releases/latest" \ + | jq -r '.assets[] | select(.name=="Release.tar.gz").browser_download_url')"; \ + fi; \ + wget -q "${release_url}" -O /tmp/Release.tar.gz; \ + mkdir -p "${TEAMSERVER_HOME}/Release"; \ + tar xf /tmp/Release.tar.gz --strip-components=1 -C "${TEAMSERVER_HOME}/Release"; \ + rm /tmp/Release.tar.gz # Add the entrypoint script directly RUN cat > /usr/local/bin/teamserver-entrypoint.sh <<'EOF' @@ -38,10 +44,8 @@ TEAMSERVER_BIN="${TEAMSERVER_DIR}/TeamServer" if [ ! -x "${TEAMSERVER_BIN}" ]; then cat >&2 <<'MSG' [TeamServer] TeamServer binary was not found at /opt/teamserver/Release/TeamServer/TeamServer. -[TeamServer] Mount a populated Release directory into the container, for example: -[TeamServer] docker run --rm --network host \ -[TeamServer] -v /path/to/Release:/opt/teamserver/Release \ -[TeamServer] exploration-teamserver:latest +[TeamServer] The image normally ships with a bundled Release directory. +[TeamServer] If you want to override it, mount your own bundle on /opt/teamserver/Release. MSG exit 1 fi @@ -59,8 +63,6 @@ RUN chmod +x /usr/local/bin/teamserver-entrypoint.sh \ chmod +x "${TEAMSERVER_HOME}/Release/TeamServer/TeamServer"; \ fi -VOLUME ["/opt/teamserver/Release"] - -EXPOSE 50051 80 443 445 +EXPOSE 50051 80 443 8443 ENTRYPOINT ["/usr/local/bin/teamserver-entrypoint.sh"] diff --git a/README.md b/README.md index 442320e..605e2fb 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ To launch the TeamServer: ```bash cd Release +cd TeamServer ./TeamServer ``` @@ -69,42 +70,35 @@ cd Release ### **🐳 Docker Deployment** -If you prefer containerized execution (recommended to avoid host library issues), build and run the Dockerfile: +If you prefer containerized execution, the repository `Dockerfile` now runs the packaged TeamServer bundle directly: ```bash -# 0) Get Dockerfile -curl -sL https://raw.githubusercontent.com/maxDcb/C2TeamServer/refs/heads/master/Dockerfile -o Dockerfile - -# 1) Build -sudo docker build -t exploration-teamserver . - -# 2) Create a host copy of the release -CID=$(sudo docker create exploration-teamserver) -sudo docker cp "$CID":/opt/teamserver/Release /opt/C2TeamServer -sudo docker rm "$CID" - -# 3) Run container with host Release mounted (for easy editing) -sudo docker run -it --rm --name exploration-teamserver -v /opt/C2TeamServer/Release:/opt/teamserver/Release -p 50051:50051 -p 80:80 -p 443:443 -p 8443:8443 exploration-teamserver +docker build -t exploration-teamserver . +docker run --rm -it \ + --name exploration-teamserver \ + -p 50051:50051 \ + -p 80:80 \ + -p 443:443 \ + -p 8443:8443 \ + exploration-teamserver ``` +The image downloads the latest `Release.tar.gz` during the image build and starts `/opt/teamserver/Release/TeamServer/TeamServer`. +If you want to override the packaged bundle with a local one, mount your own `Release` directory on `/opt/teamserver/Release`. + --- ### **💻 Installing and Running the Client** -Install the Python client using [uv](https://docs.astral.sh/uv/getting-started/installation/): +Install the Python client from the staged release bundle or from the repository sources. -```bash -# uv -uv tool install git+https://github.com/maxDcb/C2TeamServer.git#subdirectory=C2Client -``` - -Set the path to the TeamServer certificate, if you run in a docker you can simply cp the `server.crt`, or if you follow the above section it should be in `/opt/C2TeamServer/TeamServer/server.crt`: +If you use a staged release bundle, set the path to the TeamServer certificate: ```bash -export C2_CERT_PATH=/path/to/teamserver/cert/server.crt +export C2_CERT_PATH=/path/to/Release/TeamServer/server.crt ``` -Connect to the TeamServer: +Then connect to the TeamServer: ```bash c2client --ip 127.0.0.1 --port 50051 @@ -134,49 +128,66 @@ The added emojis help bring some fun and engagement to the titles and sections, ## 🛠️ Build -The **Exploration C2 Framework** consists of multiple components, including the **C2TeamServer**, **C2Implant**, and **C2LinuxImplant**. Below are the build instructions for **C2TeamServer**. +The repository now uses: + +- `Conan` for C/C++ dependencies +- `CMake` for configuration +- `GNU Make` or another CMake generator for compilation +- `pyproject.toml` and `requirements.txt` for Python dependencies + +### 🔧 Build From Scratch -### 🔧 Build Process +Validated commands in WSL/Linux: -1. **Install Dependencies**: +```bash +git clone https://github.com/maxDcb/C2TeamServer.git +cd C2TeamServer +git submodule update --init --recursive - * Ensure you have **CMake**, **g++**, and **Conan** installed. +python3 -m pip install --upgrade "conan==2.24.0" - ```bash - sudo apt install cmake - pip3 install conan - ``` +cmake -B build -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=$PWD/conan_provider.cmake +cmake --build build -j"$(nproc)" +ctest --test-dir build --output-on-failure +``` -2. **Clone the Repository**: - If you haven't cloned the repository already, you can do so with: +Notes: - ```bash - git clone https://github.com/maxDcb/C2TeamServer.git - cd C2TeamServer - git submodule update --init - ``` +- Use the absolute path to `conan_provider.cmake`. The old relative example was not reliable. +- Build artifacts are generated in the build tree, not written back into the repository root. +- The validated build currently runs `53` CTest tests from the root build. -3. **Configure the Build**: +### 🧪 Client Tests - * Create a build directory and navigate into it. +The client depends on the generated Python protocol package produced by the root CMake build: - ```bash - mkdir build - cd build - ``` +```bash +cd C2Client +python -m venv .venv +. .venv/bin/activate +pip install -e .[test] - * Run CMake to configure the build process. This may take some time if you haven't installed the necessary dependencies yet, which are provided by Conan. +export C2_PROTOCOL_PYTHON_ROOT=$PWD/../build/generated/python_protocol +pytest tests -q +``` - ```bash - cmake .. -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./conan_provider.cmake - ``` +### 📦 Stage A Release Bundle -4. **Build the TeamServer**: +To assemble the local release bundle from build outputs: - * Now, you can compile the TeamServer with the following command: +```bash +cmake --build build --target stage_release_bundle +``` + +The bundle is created under: + +```text +build/release-staging/Release +``` - ```bash - make - ``` +This staged bundle contains: - * This will generate the `TeamServer` binary along with the TeamServer modules and copy them into the `Release` folder in the root directory of the project. +- `TeamServer` +- `TeamServerModules` +- the generated Python client protocol package +- the Python client sources and launchers diff --git a/packaging/assemble_release.py b/packaging/assemble_release.py index ec8135d..7dc9fe9 100644 --- a/packaging/assemble_release.py +++ b/packaging/assemble_release.py @@ -90,13 +90,10 @@ def _build_client_bundle(source_root: Path, build_root: Path, release_root: Path def assemble_release(source_root: Path, build_root: Path, output_root: Path) -> None: - tracked_release_root = source_root / "Release" build_release_root = build_root / "artifacts" / "Release" teamserver_root = build_release_root / "TeamServer" modules_root = build_release_root / "TeamServerModules" - if not tracked_release_root.exists(): - raise FileNotFoundError(f"Missing tracked release assets directory: {tracked_release_root}") if not teamserver_root.exists(): raise FileNotFoundError( f"Missing TeamServer runtime artifacts: {teamserver_root}. " @@ -110,7 +107,7 @@ def assemble_release(source_root: Path, build_root: Path, output_root: Path) -> shutil.rmtree(output_root, ignore_errors=True) output_root.parent.mkdir(parents=True, exist_ok=True) - _copytree(tracked_release_root, output_root) + output_root.mkdir(parents=True, exist_ok=True) shutil.rmtree(output_root / "TeamServer", ignore_errors=True) shutil.rmtree(output_root / "TeamServerModules", ignore_errors=True) @@ -118,6 +115,8 @@ def assemble_release(source_root: Path, build_root: Path, output_root: Path) -> _copytree(teamserver_root, output_root / "TeamServer") _copytree(modules_root, output_root / "TeamServerModules") + shutil.rmtree(output_root / "TeamServer" / "logs", ignore_errors=True) + (output_root / "TeamServer" / "logs").mkdir(parents=True, exist_ok=True) _build_client_bundle(source_root, build_root, output_root) From bd4e0e51ada366cd84d2390a74cea5bd9487e3b2 Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:05:33 +0200 Subject: [PATCH 18/28] Add --- .github/workflows/Release.yml | 11 +- .github/workflows/Tests.yml | 24 +- AGENT.md | 18 +- C2Client/pyproject.toml | 2 +- C2Client/requirements.txt | 2 +- CMakeLists.txt | 72 +++-- README.md | 46 ++- conan.lock | 25 ++ conan/profiles/linux-gcc13 | 8 + conan_provider.cmake | 18 +- integration/CMakeLists.txt | 44 +++ integration/README.md | 39 +++ integration/tests/README.md | 13 + .../TeamServerRuntimeIntegrationTests.cpp | 294 ++++++++++++++++++ packaging/CMakeLists.txt | 9 +- 15 files changed, 582 insertions(+), 43 deletions(-) create mode 100644 conan.lock create mode 100644 conan/profiles/linux-gcc13 create mode 100644 integration/CMakeLists.txt create mode 100644 integration/README.md create mode 100644 integration/tests/README.md create mode 100644 integration/tests/TeamServerRuntimeIntegrationTests.cpp diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index df9cc53..0bfce7d 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -39,11 +39,14 @@ jobs: - name: Install Conan run: python -m pip install --upgrade pip "conan==2.24.0" - - name: Create default profile - run: conan profile detect --force - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DWITH_TESTS=OFF -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake + run: > + cmake -B ${{github.workspace}}/build + -DWITH_TESTS=OFF + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake + -DCONAN_HOST_PROFILE=${{github.workspace}}/conan/profiles/linux-gcc13 + -DCONAN_BUILD_PROFILE=${{github.workspace}}/conan/profiles/linux-gcc13 + -DCONAN_LOCKFILE=${{github.workspace}}/conan.lock - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 18 diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml index 218e983..918072a 100644 --- a/.github/workflows/Tests.yml +++ b/.github/workflows/Tests.yml @@ -40,11 +40,13 @@ jobs: - name: Install Conan run: python -m pip install --upgrade pip "conan==2.24.0" - - name: Create default profile - run: conan profile detect --force - - name: Configure CMake for tests - run: cmake -B ${{github.workspace}}/build -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake + run: > + cmake -B ${{github.workspace}}/build + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake + -DCONAN_HOST_PROFILE=${{github.workspace}}/conan/profiles/linux-gcc13 + -DCONAN_BUILD_PROFILE=${{github.workspace}}/conan/profiles/linux-gcc13 + -DCONAN_LOCKFILE=${{github.workspace}}/conan.lock - name: Build for test run: cmake --build ${{github.workspace}}/build -j 18 @@ -68,8 +70,20 @@ jobs: test -x "${{github.workspace}}/build/release-staging/Release/TeamServer/TeamServer" test -f "${{github.workspace}}/build/release-staging/Release/Client/c2client_protocol/TeamServerApi_pb2.py" + - name: Prepare integration runtime smoke test + run: | + cmake --build ${{github.workspace}}/build --target stage_integration_runtime + test -x "${{github.workspace}}/build/integration-staging/runtime/Release/TeamServer/TeamServer" + test -f "${{github.workspace}}/build/integration-staging/runtime/Release/Client/c2client_protocol/TeamServerApi_pb2.py" + - name: Configure CMake like the release - run: cmake -B ${{github.workspace}}/buildRelease -DWITH_TESTS=OFF -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake + run: > + cmake -B ${{github.workspace}}/buildRelease + -DWITH_TESTS=OFF + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake + -DCONAN_HOST_PROFILE=${{github.workspace}}/conan/profiles/linux-gcc13 + -DCONAN_BUILD_PROFILE=${{github.workspace}}/conan/profiles/linux-gcc13 + -DCONAN_LOCKFILE=${{github.workspace}}/conan.lock - name: Build like the release run: cmake --build ${{github.workspace}}/buildRelease -j 18 diff --git a/AGENT.md b/AGENT.md index b061091..379ce99 100644 --- a/AGENT.md +++ b/AGENT.md @@ -17,6 +17,7 @@ From now on, the project must be compiled and tested during implementation work - Use Conan for dependencies, CMake for configuration, and GNU Make with GCC for the build. - After code changes, run at least the project configure step, the build, and `ctest --output-on-failure` when feasible. - If the change touches the Python client or generated protocol bindings, also run the client pytest suite with `C2_PROTOCOL_PYTHON_ROOT` pointing at the build tree. +- If the change touches packaging, top-level layout, or release/runtime assembly, also validate `stage_release_bundle`. For future integration work, prefer validating `stage_integration_runtime` too. ## Validated Environment @@ -35,7 +36,11 @@ Run the project from scratch with: cd /home/max/project/C2TeamServer mkdir -p build-codex-scratch cd build-codex-scratch -cmake .. -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=/home/max/project/C2TeamServer/conan_provider.cmake +cmake .. \ + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=/home/max/project/C2TeamServer/conan_provider.cmake \ + -DCONAN_HOST_PROFILE=/home/max/project/C2TeamServer/conan/profiles/linux-gcc13 \ + -DCONAN_BUILD_PROFILE=/home/max/project/C2TeamServer/conan/profiles/linux-gcc13 \ + -DCONAN_LOCKFILE=/home/max/project/C2TeamServer/conan.lock make ctest --output-on-failure ``` @@ -54,14 +59,23 @@ Notes: - The `README.md` example using `-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./conan_provider.cmake` was not reliable in the validated setup. - Use the absolute path to `conan_provider.cmake` shown above. -- The validated root build currently executes `53` CTest tests and `5` Python client tests. +- Prefer the checked-in Conan profile and lockfile to keep the graph stable across local builds and CI. +- The validated root build currently executes `54` CTest tests and `5` Python client tests. - The staged release bundle is produced with `cmake --build --target stage_release_bundle` under `/release-staging/Release`. +- The staged integration runtime is produced with `cmake --build --target stage_integration_runtime` under `/integration-staging/runtime/Release`. ## Responsibilities - Analyze and explain C++ classes, actions, and CMake configuration. - Trace behavior from gRPC definitions to implementation. - Keep edits consistent with the existing module structure and naming. +- Respect the repository boundaries: + - `protocol/` for `.proto` and stub generation + - `teamServer/` for the server + - `C2Client/` for the Python client + - `core/` for source-shared C++ code + - `packaging/` for bundle assembly + - `integration/` for end-to-end test scaffolding - Update documentation when build or workflow details change. - Verify that code changes still configure, compile, and test correctly. diff --git a/C2Client/pyproject.toml b/C2Client/pyproject.toml index 6a5cf1c..5e2be27 100644 --- a/C2Client/pyproject.toml +++ b/C2Client/pyproject.toml @@ -8,7 +8,7 @@ version = "0.1.0" dependencies = [ "setuptools", "pycryptodome==3.23.0", - "grpcio==1.74.0", + "grpcio==1.78.0", "PyQt6==6.7.0", "pyqtdarktheme", "protobuf==6.33.5", diff --git a/C2Client/requirements.txt b/C2Client/requirements.txt index fac2b5d..3ba9826 100644 --- a/C2Client/requirements.txt +++ b/C2Client/requirements.txt @@ -1,5 +1,5 @@ pycryptodome==3.23.0 -grpcio==1.74.0 +grpcio==1.78.0 PyQt6==6.7.0 pyqtdarktheme protobuf==6.33.5 diff --git a/CMakeLists.txt b/CMakeLists.txt index ae8b85b..2ae6f30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,22 +3,45 @@ cmake_minimum_required(VERSION 3.24.0 FATAL_ERROR) project(C2TeamServer VERSION 0.0.0 LANGUAGES CXX C) set(CMAKE_BUILD_TYPE Release) set(CMAKE_CXX_STANDARD 17) - + set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) +# +# Repository boundaries. +# +# The repository still keeps a few historical directory names (`teamServer`, +# `C2Client`) but the root build treats them as the long-term monorepo areas +# below so a future repo split stays mechanical. +# +set(C2_PROTOCOL_DIR "${CMAKE_SOURCE_DIR}/protocol") +set(C2_SERVER_DIR "${CMAKE_SOURCE_DIR}/teamServer") +set(C2_CLIENT_DIR "${CMAKE_SOURCE_DIR}/C2Client") +set(C2_CORE_DIR "${CMAKE_SOURCE_DIR}/core") +set(C2_PACKAGING_DIR "${CMAKE_SOURCE_DIR}/packaging") +set(C2_CERTS_DIR "${CMAKE_SOURCE_DIR}/certs") +set(C2_THIRDPARTY_DIR "${CMAKE_SOURCE_DIR}/thirdParty") +set(C2_LIBS_DIR "${CMAKE_SOURCE_DIR}/libs") +set(C2_INTEGRATION_DIR "${CMAKE_SOURCE_DIR}/integration") + set(C2_BUILD_ARTIFACT_ROOT "${CMAKE_BINARY_DIR}/artifacts") set(C2_RUNTIME_ROOT "${C2_BUILD_ARTIFACT_ROOT}/Release") set(C2_TEAMSERVER_RUNTIME_OUTPUT_DIR "${C2_RUNTIME_ROOT}/TeamServer") set(C2_RUNTIME_MODULE_OUTPUT_DIR "${C2_RUNTIME_ROOT}/TeamServerModules") set(C2_TEST_BIN_OUTPUT_DIR "${CMAKE_BINARY_DIR}/tests/bin") set(C2_GENERATED_PYTHON_GRPC_DIR "${CMAKE_BINARY_DIR}/generated/python_grpc") +set(C2_RELEASE_STAGING_ROOT "${CMAKE_BINARY_DIR}/release-staging") +set(C2_RELEASE_STAGING_DIR "${C2_RELEASE_STAGING_ROOT}/Release") +set(C2_INTEGRATION_STAGING_ROOT "${CMAKE_BINARY_DIR}/integration-staging") +set(C2_INTEGRATION_STAGING_DIR "${C2_INTEGRATION_STAGING_ROOT}/runtime") file(MAKE_DIRECTORY "${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}" "${C2_RUNTIME_MODULE_OUTPUT_DIR}" "${C2_TEST_BIN_OUTPUT_DIR}" "${C2_GENERATED_PYTHON_GRPC_DIR}" + "${C2_RELEASE_STAGING_DIR}" + "${C2_INTEGRATION_STAGING_DIR}" ) @@ -88,35 +111,38 @@ endif() ## Build ## -include_directories(thirdParty) - -add_subdirectory(libs) -add_subdirectory(protocol) +include_directories("${C2_THIRDPARTY_DIR}") -add_subdirectory(thirdParty) -include_directories(thirdParty/base64) -include_directories(thirdParty/donut/include) - -set(DONUT_BUILD_DIR "${CMAKE_BINARY_DIR}/thirdParty/donut") -add_library(Donut STATIC IMPORTED) -set_target_properties(Donut PROPERTIES -IMPORTED_LOCATION "${DONUT_BUILD_DIR}/lib/libdonut.a" -) +add_subdirectory("${C2_LIBS_DIR}") +add_subdirectory("${C2_PROTOCOL_DIR}") + +add_subdirectory("${C2_THIRDPARTY_DIR}") +include_directories("${C2_THIRDPARTY_DIR}/base64") +include_directories("${C2_THIRDPARTY_DIR}/donut/include") + +set(DONUT_BUILD_DIR "${CMAKE_BINARY_DIR}/thirdParty/donut") +add_library(Donut STATIC IMPORTED) +set_target_properties(Donut PROPERTIES +IMPORTED_LOCATION "${DONUT_BUILD_DIR}/lib/libdonut.a" +) if(WITH_TESTS) enable_testing() endif() -include_directories(core/listener) -include_directories(core/beacon) -include_directories(core/modules/ModuleCmd) - -add_subdirectory(teamServer) -add_subdirectory(core/modules) +include_directories("${C2_CORE_DIR}/listener") +include_directories("${C2_CORE_DIR}/beacon") +include_directories("${C2_CORE_DIR}/modules/ModuleCmd") -add_subdirectory(certs) -add_subdirectory(packaging) - +add_subdirectory("${C2_SERVER_DIR}") +add_subdirectory("${C2_CORE_DIR}/modules") + +add_subdirectory("${C2_CERTS_DIR}") +add_subdirectory("${C2_PACKAGING_DIR}") + +if(WITH_TESTS) + add_subdirectory("${C2_INTEGRATION_DIR}") +endif() diff --git a/README.md b/README.md index 605e2fb..255e3c5 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,24 @@ Supported communication channels include: `TCP`, `SMB`, `HTTP`, and `HTTPS`. --- +## Repository Layout + +The repository is kept as a monorepo for now, with boundaries aligned to the +future split target: + +- `protocol/`: `.proto` source of truth and generated gRPC build rules +- `teamServer/`: server runtime and gRPC implementation +- `C2Client/`: Python client package and UI +- `core/`: source-shared C++ components reused by multiple projects +- `packaging/`: release bundle assembly +- `integration/`: staging area and future end-to-end tests + +The historical directory names `teamServer` and `C2Client` are still present, +but the root build now treats them explicitly as the `server` and `client` +areas. + +--- + ## **⚡ Quick Start** ### **🖥️ Running the TeamServer** @@ -146,7 +164,11 @@ git submodule update --init --recursive python3 -m pip install --upgrade "conan==2.24.0" -cmake -B build -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=$PWD/conan_provider.cmake +cmake -B build \ + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=$PWD/conan_provider.cmake \ + -DCONAN_HOST_PROFILE=$PWD/conan/profiles/linux-gcc13 \ + -DCONAN_BUILD_PROFILE=$PWD/conan/profiles/linux-gcc13 \ + -DCONAN_LOCKFILE=$PWD/conan.lock cmake --build build -j"$(nproc)" ctest --test-dir build --output-on-failure ``` @@ -154,8 +176,9 @@ ctest --test-dir build --output-on-failure Notes: - Use the absolute path to `conan_provider.cmake`. The old relative example was not reliable. +- The repository now ships a Linux/GCC 13 Conan profile in `conan/profiles/linux-gcc13` and a checked-in `conan.lock` to freeze the resolved dependency graph used in CI and documented local builds. - Build artifacts are generated in the build tree, not written back into the repository root. -- The validated build currently runs `53` CTest tests from the root build. +- The validated build currently runs `54` CTest tests from the root build, including one staged-runtime integration test. ### 🧪 Client Tests @@ -191,3 +214,22 @@ This staged bundle contains: - `TeamServerModules` - the generated Python client protocol package - the Python client sources and launchers + +### Prepare The Integration Runtime + +The root build provides a dedicated preparation target for integration tests: + +```bash +cmake --build build --target stage_integration_runtime +``` + +This produces a deterministic runtime under: + +```text +build/integration-staging/runtime/Release +``` + +That staged runtime is already used by a first smoke integration test covering +TeamServer startup, gRPC authentication, and stable empty-state RPCs. It is the +base for the next round of end-to-end tests around the packaged Python client +and deeper contract validation. diff --git a/conan.lock b/conan.lock new file mode 100644 index 0000000..7a04ad8 --- /dev/null +++ b/conan.lock @@ -0,0 +1,25 @@ +{ + "version": "0.5", + "requires": [ + "zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1765284699.337", + "spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1767636069.964", + "re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1772560729.95", + "protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1773224203.27", + "openssl/3.6.2#36f92686a9285adfe2ccd1c1dbf9ea35%1775635547.022", + "grpc/1.78.1#b1a9e74b145cc471bed4dc64dc6eb2c1%1772623605.068", + "fmt/12.1.0#50abab23274d56bb8f42c94b3b9a40c7%1761885265.231", + "crowcpp-crow/1.3.0#33d33f4f5364b02670c257aeebec4f82%1762886766.263", + "cpp-httplib/0.39.0#034a1d73b584c90da8ecc35d6fd277cf%1774519798.21", + "c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1766500685.317", + "asio/1.29.0#52d17a857aa6f978d8c17d7c38eae5ad%1702236346.321", + "abseil/20250127.0#4242e8b46c00cdfbeb976f3d02990ba6%1761741491.734" + ], + "build_requires": [ + "zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1765284699.337", + "protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1773224203.27", + "cmake/3.31.10#313d16a1aa16bbdb2ca0792467214b76%1763665505.054", + "abseil/20250127.0#4242e8b46c00cdfbeb976f3d02990ba6%1761741491.734" + ], + "python_requires": [], + "config_requires": [] +} diff --git a/conan/profiles/linux-gcc13 b/conan/profiles/linux-gcc13 new file mode 100644 index 0000000..1f43c97 --- /dev/null +++ b/conan/profiles/linux-gcc13 @@ -0,0 +1,8 @@ +[settings] +arch=x86_64 +os=Linux +compiler=gcc +compiler.version=13 +compiler.cppstd=17 +compiler.libcxx=libstdc++11 +build_type=Release diff --git a/conan_provider.cmake b/conan_provider.cmake index 6bf31b1..6678be6 100644 --- a/conan_provider.cmake +++ b/conan_provider.cmake @@ -546,6 +546,16 @@ macro(conan_provide_dependency method package_name) endif() construct_profile_argument(_host_profile_flags CONAN_HOST_PROFILE) construct_profile_argument(_build_profile_flags CONAN_BUILD_PROFILE) + set(_conan_lockfile_args "") + if(CONAN_LOCKFILE) + cmake_path(ABSOLUTE_PATH CONAN_LOCKFILE BASE_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE _conan_lockfile_path) + if(EXISTS "${_conan_lockfile_path}") + list(APPEND _conan_lockfile_args "--lockfile=${_conan_lockfile_path}") + message(STATUS "CMake-Conan: using lockfile ${_conan_lockfile_path}") + else() + message(FATAL_ERROR "CMake-Conan: requested lockfile does not exist: ${_conan_lockfile_path}") + endif() + endif() if(EXISTS "${CMAKE_SOURCE_DIR}/conanfile.py") file(READ "${CMAKE_SOURCE_DIR}/conanfile.py" outfile) if(NOT "${outfile}" MATCHES ".*CMakeDeps.*") @@ -563,14 +573,15 @@ macro(conan_provide_dependency method package_name) get_property(_multiconfig_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(NOT _multiconfig_generator) message(STATUS "CMake-Conan: Installing single configuration ${CMAKE_BUILD_TYPE}") - conan_install(${_host_profile_flags} ${_build_profile_flags} ${CONAN_INSTALL_ARGS} ${generator}) + conan_install(${_host_profile_flags} ${_build_profile_flags} ${_conan_lockfile_args} ${CONAN_INSTALL_ARGS} ${generator}) else() message(STATUS "CMake-Conan: Installing both Debug and Release") - conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Release ${CONAN_INSTALL_ARGS} ${generator}) - conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Debug ${CONAN_INSTALL_ARGS} ${generator}) + conan_install(${_host_profile_flags} ${_build_profile_flags} ${_conan_lockfile_args} -s build_type=Release ${CONAN_INSTALL_ARGS} ${generator}) + conan_install(${_host_profile_flags} ${_build_profile_flags} ${_conan_lockfile_args} -s build_type=Debug ${CONAN_INSTALL_ARGS} ${generator}) endif() unset(_host_profile_flags) unset(_build_profile_flags) + unset(_conan_lockfile_args) unset(_multiconfig_generator) unset(_conan_install_success) else() @@ -640,6 +651,7 @@ cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" CALL conan_provide_dependen # Configurable variables for Conan profiles set(CONAN_HOST_PROFILE "default;auto-cmake" CACHE STRING "Conan host profile") set(CONAN_BUILD_PROFILE "default" CACHE STRING "Conan build profile") +set(CONAN_LOCKFILE "" CACHE FILEPATH "Optional Conan lockfile") set(CONAN_INSTALL_ARGS "--build=missing" CACHE STRING "Command line arguments for conan install") find_program(_cmake_program NAMES cmake NO_PACKAGE_ROOT_PATH NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH NO_CMAKE_FIND_ROOT_PATH) diff --git a/integration/CMakeLists.txt b/integration/CMakeLists.txt new file mode 100644 index 0000000..dfc92aa --- /dev/null +++ b/integration/CMakeLists.txt @@ -0,0 +1,44 @@ +find_package(Python3 COMPONENTS Interpreter REQUIRED) + +set(C2_INTEGRATION_RUNTIME_RELEASE_DIR "${C2_INTEGRATION_STAGING_DIR}/Release") + +add_custom_target(stage_integration_runtime + COMMAND ${CMAKE_COMMAND} -E rm -rf "${C2_INTEGRATION_STAGING_DIR}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${C2_INTEGRATION_RUNTIME_RELEASE_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy_directory "${C2_RELEASE_STAGING_DIR}" "${C2_INTEGRATION_RUNTIME_RELEASE_DIR}" + DEPENDS stage_release_bundle + COMMENT "Preparing integration runtime from staged release bundle" + VERBATIM +) + +add_executable(testsTeamServerRuntimeIntegration + tests/TeamServerRuntimeIntegrationTests.cpp +) + +target_compile_definitions(testsTeamServerRuntimeIntegration PRIVATE + C2_INTEGRATION_STAGING_DIR="${C2_INTEGRATION_STAGING_DIR}" +) + +if(WIN32) + target_link_libraries(testsTeamServerRuntimeIntegration + GrpcMessages + gRPC::grpc++ + protobuf::libprotobuf + nlohmann_json::nlohmann_json + ) +else() + target_link_libraries(testsTeamServerRuntimeIntegration + GrpcMessages + gRPC::grpc++ + protobuf::libprotobuf + nlohmann_json::nlohmann_json + pthread + ) +endif() + +add_dependencies(testsTeamServerRuntimeIntegration stage_integration_runtime) + +add_custom_command(TARGET testsTeamServerRuntimeIntegration POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $ "${C2_TEST_BIN_OUTPUT_DIR}/$") + +add_test(NAME testsTeamServerRuntimeIntegration COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") diff --git a/integration/README.md b/integration/README.md new file mode 100644 index 0000000..a96d253 --- /dev/null +++ b/integration/README.md @@ -0,0 +1,39 @@ +# Integration + +This directory is the boundary for end-to-end and contract-style integration +tests. It now provides both a stable runtime staging target and a first +TeamServer smoke test that exercises the staged runtime through real gRPC. + +## Current Contract + +- Source of truth for the test runtime: `build/integration-staging/runtime/Release` +- Preparation target: `cmake --build --target stage_integration_runtime` +- First integration test: `testsTeamServerRuntimeIntegration` +- Inputs: + - `stage_release_bundle` + - TeamServer binary and runtime config + - TeamServer modules + - Python client sources + - generated `c2client_protocol` package + +## Current Coverage + +`testsTeamServerRuntimeIntegration` copies the staged runtime to a temporary +directory, rewrites the gRPC port, starts the real TeamServer binary, performs +an authenticated gRPC round-trip, and verifies stable empty-state RPCs: + +- `Authenticate` +- `GetListeners` +- `GetSessions` + +## Next Step + +The next integration-test phase should extend `integration/tests/` around: + +- client transport/auth handshake through the Python client package +- a small gRPC contract smoke test beyond the empty-state RPCs +- one release-bundle smoke test to validate the packaged layout + +The goal is to make integration tests run against the same staged runtime that +the release pipeline assembles, so packaging regressions and protocol drift are +caught before release. diff --git a/integration/tests/README.md b/integration/tests/README.md new file mode 100644 index 0000000..3767b2b --- /dev/null +++ b/integration/tests/README.md @@ -0,0 +1,13 @@ +# Planned Integration Scenarios + +This directory now contains the first staged-runtime integration test: + +- `TeamServerRuntimeIntegrationTests.cpp` + +It is driven from the runtime prepared by `stage_integration_runtime`. + +Recommended first scenarios: + +1. Python client can import `c2client_protocol` from the staged bundle +2. Python client can authenticate against the staged TeamServer +3. One stable RPC smoke path validates server/client/protocol compatibility diff --git a/integration/tests/TeamServerRuntimeIntegrationTests.cpp b/integration/tests/TeamServerRuntimeIntegrationTests.cpp new file mode 100644 index 0000000..8529f83 --- /dev/null +++ b/integration/tests/TeamServerRuntimeIntegrationTests.cpp @@ -0,0 +1,294 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "TeamServerApi.grpc.pb.h" + +namespace fs = std::filesystem; + +namespace +{ +class ScopedPath +{ +public: + explicit ScopedPath(fs::path path) + : m_path(std::move(path)) + { + } + + ~ScopedPath() + { + std::error_code ec; + fs::remove_all(m_path, ec); + } + + const fs::path& path() const + { + return m_path; + } + +private: + fs::path m_path; +}; + +class ScopedServerProcess +{ +public: + explicit ScopedServerProcess(fs::path runtimeRoot) + : m_runtimeRoot(std::move(runtimeRoot)) + { + } + + ~ScopedServerProcess() + { + stop(); + } + + void start() + { + assert(m_pid == -1); + + fs::path teamServerDir = m_runtimeRoot / "TeamServer"; + fs::path teamServerBinary = teamServerDir / "TeamServer"; + assert(fs::exists(teamServerBinary)); + + m_pid = ::fork(); + assert(m_pid >= 0); + + if (m_pid == 0) + { + const int chdirResult = ::chdir(teamServerDir.c_str()); + if (chdirResult != 0) + _exit(126); + ::execl(teamServerBinary.c_str(), teamServerBinary.c_str(), "TeamServerConfig.json", static_cast(nullptr)); + _exit(127); + } + } + + bool isRunning() const + { + if (m_pid <= 0) + return false; + + int status = 0; + pid_t result = ::waitpid(m_pid, &status, WNOHANG); + return result == 0; + } + + void stop() + { + if (m_pid <= 0) + return; + + if (isRunning()) + { + ::kill(m_pid, SIGTERM); + + for (int attempt = 0; attempt < 20; ++attempt) + { + int status = 0; + pid_t result = ::waitpid(m_pid, &status, WNOHANG); + if (result == m_pid) + { + m_pid = -1; + return; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + ::kill(m_pid, SIGKILL); + } + + int status = 0; + ::waitpid(m_pid, &status, 0); + m_pid = -1; + } + +private: + fs::path m_runtimeRoot; + pid_t m_pid = -1; +}; + +std::string readFile(const fs::path& filePath) +{ + std::ifstream input(filePath, std::ios::binary); + return std::string(std::istreambuf_iterator(input), {}); +} + +int reserveTcpPort() +{ + int fd = ::socket(AF_INET, SOCK_STREAM, 0); + assert(fd >= 0); + + sockaddr_in address {}; + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address.sin_port = htons(0); + + int bindResult = ::bind(fd, reinterpret_cast(&address), sizeof(address)); + assert(bindResult == 0); + + socklen_t addressLength = sizeof(address); + int nameResult = ::getsockname(fd, reinterpret_cast(&address), &addressLength); + assert(nameResult == 0); + + int port = ntohs(address.sin_port); + ::close(fd); + return port; +} + +fs::path makeRuntimeCopy(const fs::path& stagedRuntimeRoot) +{ + fs::path tempRoot = fs::temp_directory_path() / ("c2teamserver-integration-" + std::to_string(::getpid())); + fs::remove_all(tempRoot); + fs::create_directories(tempRoot); + + fs::path runtimeCopy = tempRoot / "runtime"; + fs::copy(stagedRuntimeRoot, runtimeCopy, fs::copy_options::recursive); + return runtimeCopy; +} + +void rewriteRuntimeConfig(const fs::path& runtimeRoot, int grpcPort) +{ + fs::path configFile = runtimeRoot / "TeamServer" / "TeamServerConfig.json"; + std::ifstream input(configFile); + nlohmann::json config = nlohmann::json::parse(input); + + config["ServerGRPCAdd"] = "127.0.0.1"; + config["ServerGRPCPort"] = std::to_string(grpcPort); + + std::ofstream output(configFile); + output << config.dump(4); +} + +std::unique_ptr makeStub(const fs::path& runtimeRoot, int grpcPort) +{ + grpc::SslCredentialsOptions credentialsOptions; + credentialsOptions.pem_root_certs = readFile(runtimeRoot / "TeamServer" / "rootCA.crt"); + + grpc::ChannelArguments channelArguments; + channelArguments.SetSslTargetNameOverride("localhost"); + channelArguments.SetMaxReceiveMessageSize(512 * 1024 * 1024); + channelArguments.SetMaxSendMessageSize(512 * 1024 * 1024); + + auto channel = grpc::CreateCustomChannel( + "127.0.0.1:" + std::to_string(grpcPort), + grpc::SslCredentials(credentialsOptions), + channelArguments); + return teamserverapi::TeamServerApi::NewStub(channel); +} + +std::string authenticate(teamserverapi::TeamServerApi::Stub& stub) +{ + grpc::ClientContext context; + teamserverapi::AuthRequest request; + request.set_username("admin"); + request.set_password("admin"); + + teamserverapi::AuthResponse response; + grpc::Status status = stub.Authenticate(&context, request, &response); + + assert(status.ok()); + assert(response.status() == teamserverapi::OK); + assert(!response.token().empty()); + return response.token(); +} + +void waitForServerReady(teamserverapi::TeamServerApi::Stub& stub, ScopedServerProcess& process) +{ + for (int attempt = 0; attempt < 50; ++attempt) + { + assert(process.isRunning()); + + grpc::ClientContext context; + teamserverapi::AuthRequest request; + request.set_username("admin"); + request.set_password("admin"); + + teamserverapi::AuthResponse response; + grpc::Status status = stub.Authenticate(&context, request, &response); + if (status.ok() && response.status() == teamserverapi::OK && !response.token().empty()) + return; + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + assert(false && "TeamServer did not become ready in time"); +} + +void testStagedRuntimeSupportsGrpcAuthenticationAndStableRpc() +{ + fs::path stagedRuntimeRoot = fs::path(C2_INTEGRATION_STAGING_DIR) / "Release"; + assert(fs::exists(stagedRuntimeRoot / "TeamServer" / "TeamServer")); + assert(fs::exists(stagedRuntimeRoot / "Client" / "c2client_protocol" / "TeamServerApi_pb2.py")); + + ScopedPath runtimeCopy(makeRuntimeCopy(stagedRuntimeRoot)); + const int grpcPort = reserveTcpPort(); + rewriteRuntimeConfig(runtimeCopy.path(), grpcPort); + + ScopedServerProcess process(runtimeCopy.path()); + process.start(); + + auto stub = makeStub(runtimeCopy.path(), grpcPort); + waitForServerReady(*stub, process); + + const std::string token = authenticate(*stub); + + grpc::ClientContext listenersContext; + listenersContext.AddMetadata("authorization", "Bearer " + token); + listenersContext.AddMetadata("clientid", "integration-test"); + + teamserverapi::Empty empty; + std::unique_ptr> listeners = stub->GetListeners(&listenersContext, empty); + teamserverapi::Listener listener; + std::vector streamedListeners; + while (listeners->Read(&listener)) + { + streamedListeners.push_back(listener); + } + grpc::Status listenersStatus = listeners->Finish(); + + assert(listenersStatus.ok()); + assert(streamedListeners.empty()); + + grpc::ClientContext sessionsContext; + sessionsContext.AddMetadata("authorization", "Bearer " + token); + sessionsContext.AddMetadata("clientid", "integration-test"); + + std::unique_ptr> sessions = stub->GetSessions(&sessionsContext, empty); + teamserverapi::Session session; + std::vector streamedSessions; + while (sessions->Read(&session)) + { + streamedSessions.push_back(session); + } + grpc::Status sessionsStatus = sessions->Finish(); + + assert(sessionsStatus.ok()); + assert(streamedSessions.empty()); +} +} // namespace + +int main() +{ + testStagedRuntimeSupportsGrpcAuthenticationAndStableRpc(); + return 0; +} diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt index 938ea29..eebdfc0 100644 --- a/packaging/CMakeLists.txt +++ b/packaging/CMakeLists.txt @@ -1,7 +1,12 @@ find_package(Python3 COMPONENTS Interpreter REQUIRED) -set(C2_RELEASE_STAGING_ROOT "${CMAKE_BINARY_DIR}/release-staging") -set(C2_RELEASE_STAGING_DIR "${C2_RELEASE_STAGING_ROOT}/Release") +if(NOT DEFINED C2_RELEASE_STAGING_ROOT) + set(C2_RELEASE_STAGING_ROOT "${CMAKE_BINARY_DIR}/release-staging") +endif() + +if(NOT DEFINED C2_RELEASE_STAGING_DIR) + set(C2_RELEASE_STAGING_DIR "${C2_RELEASE_STAGING_ROOT}/Release") +endif() add_custom_target(stage_release_bundle COMMAND ${Python3_EXECUTABLE} From 1fc00efa26d86ead78ebd9f0e892e1f6f05aa2f7 Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:19:29 +0200 Subject: [PATCH 19/28] Move --- CMakeLists.txt | 24 +- conan.lock | 1 + conanfile.txt | 1 + thirdParty/nlohmann/json.hpp | 24766 --------------------------------- 4 files changed, 12 insertions(+), 24780 deletions(-) delete mode 100644 thirdParty/nlohmann/json.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ae6f30..952ba03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,12 +58,16 @@ unset(GRPC_PYTHON_PLUGIN_PROGRAM CACHE) find_package(gRPC REQUIRED) find_package(OpenSSL REQUIRED) find_package(protobuf REQUIRED) -find_package(ZLIB REQUIRED) -find_package(spdlog REQUIRED) -find_package(httplib REQUIRED) -find_package(Crow REQUIRED) - -include_directories(${CMAKE_INCLUDE_PATH}) +find_package(ZLIB REQUIRED) +find_package(spdlog REQUIRED) +find_package(httplib REQUIRED) +find_package(Crow REQUIRED) +find_package(nlohmann_json REQUIRED) + +include_directories(${CMAKE_INCLUDE_PATH}) + +get_target_property(C2_NLOHMANN_JSON_INCLUDE_DIRS nlohmann_json::nlohmann_json INTERFACE_INCLUDE_DIRECTORIES) +include_directories(${C2_NLOHMANN_JSON_INCLUDE_DIRS}) ## @@ -98,12 +102,6 @@ add_definitions(-DBUILD_TEAMSERVER) if(WITH_TESTS) set(SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG) - FetchContent_Declare( - nlohmann_json - GIT_REPOSITORY https://github.com/nlohmann/json.git - GIT_TAG v3.12.0 - ) - FetchContent_MakeAvailable(nlohmann_json) endif() @@ -111,8 +109,6 @@ endif() ## Build ## -include_directories("${C2_THIRDPARTY_DIR}") - add_subdirectory("${C2_LIBS_DIR}") add_subdirectory("${C2_PROTOCOL_DIR}") diff --git a/conan.lock b/conan.lock index 7a04ad8..079e7b4 100644 --- a/conan.lock +++ b/conan.lock @@ -6,6 +6,7 @@ "re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1772560729.95", "protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1773224203.27", "openssl/3.6.2#36f92686a9285adfe2ccd1c1dbf9ea35%1775635547.022", + "nlohmann_json/3.12.0#2d634ab0ec8d9f56353e5ccef6d6612c%1744735883.94", "grpc/1.78.1#b1a9e74b145cc471bed4dc64dc6eb2c1%1772623605.068", "fmt/12.1.0#50abab23274d56bb8f42c94b3b9a40c7%1761885265.231", "crowcpp-crow/1.3.0#33d33f4f5364b02670c257aeebec4f82%1762886766.263", diff --git a/conanfile.txt b/conanfile.txt index e908308..b16314b 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -5,6 +5,7 @@ spdlog/1.17.0 cpp-httplib/0.39.0 openssl/3.6.2 crowcpp-crow/1.3.0 +nlohmann_json/3.12.0 [layout] cmake_layout diff --git a/thirdParty/nlohmann/json.hpp b/thirdParty/nlohmann/json.hpp deleted file mode 100644 index a858728..0000000 --- a/thirdParty/nlohmann/json.hpp +++ /dev/null @@ -1,24766 +0,0 @@ -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - -/****************************************************************************\ - * Note on documentation: The source files contain links to the online * - * documentation of the public API at https://json.nlohmann.me. This URL * - * contains the most recent documentation and should also be applicable to * - * previous versions; documentation for deprecated functions is not * - * removed, but marked deprecated. See "Generate documentation" section in * - * file docs/README.md. * -\****************************************************************************/ - -#ifndef INCLUDE_NLOHMANN_JSON_HPP_ -#define INCLUDE_NLOHMANN_JSON_HPP_ - -#include // all_of, find, for_each -#include // nullptr_t, ptrdiff_t, size_t -#include // hash, less -#include // initializer_list -#ifndef JSON_NO_IO - #include // istream, ostream -#endif // JSON_NO_IO -#include // random_access_iterator_tag -#include // unique_ptr -#include // string, stoi, to_string -#include // declval, forward, move, pair, swap -#include // vector - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// This file contains all macro definitions affecting or depending on the ABI - -#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK - #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) - #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3 - #warning "Already included a different version of the library!" - #endif - #endif -#endif - -#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum) - -#ifndef JSON_DIAGNOSTICS - #define JSON_DIAGNOSTICS 0 -#endif - -#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON - #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 -#endif - -#if JSON_DIAGNOSTICS - #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag -#else - #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS -#endif - -#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON - #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp -#else - #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON -#endif - -#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION - #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 -#endif - -// Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) - -#define NLOHMANN_JSON_ABI_TAGS \ - NLOHMANN_JSON_ABI_TAGS_CONCAT( \ - NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ - NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) - -// Construct the namespace version component -#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ - _v ## major ## _ ## minor ## _ ## patch -#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ - NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) - -#if NLOHMANN_JSON_NAMESPACE_NO_VERSION -#define NLOHMANN_JSON_NAMESPACE_VERSION -#else -#define NLOHMANN_JSON_NAMESPACE_VERSION \ - NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ - NLOHMANN_JSON_VERSION_MINOR, \ - NLOHMANN_JSON_VERSION_PATCH) -#endif - -// Combine namespace components -#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b -#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ - NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) - -#ifndef NLOHMANN_JSON_NAMESPACE -#define NLOHMANN_JSON_NAMESPACE \ - nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ - NLOHMANN_JSON_ABI_TAGS, \ - NLOHMANN_JSON_NAMESPACE_VERSION) -#endif - -#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN -#define NLOHMANN_JSON_NAMESPACE_BEGIN \ - namespace nlohmann \ - { \ - inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ - NLOHMANN_JSON_ABI_TAGS, \ - NLOHMANN_JSON_NAMESPACE_VERSION) \ - { -#endif - -#ifndef NLOHMANN_JSON_NAMESPACE_END -#define NLOHMANN_JSON_NAMESPACE_END \ - } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ - } // namespace nlohmann -#endif - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // transform -#include // array -#include // forward_list -#include // inserter, front_inserter, end -#include // map -#include // string -#include // tuple, make_tuple -#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible -#include // unordered_map -#include // pair, declval -#include // valarray - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // nullptr_t -#include // exception -#if JSON_DIAGNOSTICS - #include // accumulate -#endif -#include // runtime_error -#include // to_string -#include // vector - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // array -#include // size_t -#include // uint8_t -#include // string - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // declval, pair -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -template struct make_void -{ - using type = void; -}; -template using void_t = typename make_void::type; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -// https://en.cppreference.com/w/cpp/experimental/is_detected -struct nonesuch -{ - nonesuch() = delete; - ~nonesuch() = delete; - nonesuch(nonesuch const&) = delete; - nonesuch(nonesuch const&&) = delete; - void operator=(nonesuch const&) = delete; - void operator=(nonesuch&&) = delete; -}; - -template class Op, - class... Args> -struct detector -{ - using value_t = std::false_type; - using type = Default; -}; - -template class Op, class... Args> -struct detector>, Op, Args...> -{ - using value_t = std::true_type; - using type = Op; -}; - -template class Op, class... Args> -using is_detected = typename detector::value_t; - -template class Op, class... Args> -struct is_detected_lazy : is_detected { }; - -template class Op, class... Args> -using detected_t = typename detector::type; - -template class Op, class... Args> -using detected_or = detector; - -template class Op, class... Args> -using detected_or_t = typename detected_or::type; - -template class Op, class... Args> -using is_detected_exact = std::is_same>; - -template class Op, class... Args> -using is_detected_convertible = - std::is_convertible, To>; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include - - -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson -// SPDX-License-Identifier: MIT - -/* Hedley - https://nemequ.github.io/hedley - * Created by Evan Nemerson - */ - -#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) -#if defined(JSON_HEDLEY_VERSION) - #undef JSON_HEDLEY_VERSION -#endif -#define JSON_HEDLEY_VERSION 15 - -#if defined(JSON_HEDLEY_STRINGIFY_EX) - #undef JSON_HEDLEY_STRINGIFY_EX -#endif -#define JSON_HEDLEY_STRINGIFY_EX(x) #x - -#if defined(JSON_HEDLEY_STRINGIFY) - #undef JSON_HEDLEY_STRINGIFY -#endif -#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) - -#if defined(JSON_HEDLEY_CONCAT_EX) - #undef JSON_HEDLEY_CONCAT_EX -#endif -#define JSON_HEDLEY_CONCAT_EX(a,b) a##b - -#if defined(JSON_HEDLEY_CONCAT) - #undef JSON_HEDLEY_CONCAT -#endif -#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) - -#if defined(JSON_HEDLEY_CONCAT3_EX) - #undef JSON_HEDLEY_CONCAT3_EX -#endif -#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c - -#if defined(JSON_HEDLEY_CONCAT3) - #undef JSON_HEDLEY_CONCAT3 -#endif -#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) - -#if defined(JSON_HEDLEY_VERSION_ENCODE) - #undef JSON_HEDLEY_VERSION_ENCODE -#endif -#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) - -#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) - #undef JSON_HEDLEY_VERSION_DECODE_MAJOR -#endif -#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) - -#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) - #undef JSON_HEDLEY_VERSION_DECODE_MINOR -#endif -#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) - -#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) - #undef JSON_HEDLEY_VERSION_DECODE_REVISION -#endif -#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) - -#if defined(JSON_HEDLEY_GNUC_VERSION) - #undef JSON_HEDLEY_GNUC_VERSION -#endif -#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) - #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) -#elif defined(__GNUC__) - #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) -#endif - -#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) - #undef JSON_HEDLEY_GNUC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_GNUC_VERSION) - #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_MSVC_VERSION) - #undef JSON_HEDLEY_MSVC_VERSION -#endif -#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) - #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) -#elif defined(_MSC_FULL_VER) && !defined(__ICL) - #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) -#elif defined(_MSC_VER) && !defined(__ICL) - #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) -#endif - -#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) - #undef JSON_HEDLEY_MSVC_VERSION_CHECK -#endif -#if !defined(JSON_HEDLEY_MSVC_VERSION) - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) -#elif defined(_MSC_VER) && (_MSC_VER >= 1400) - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) -#elif defined(_MSC_VER) && (_MSC_VER >= 1200) - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) -#else - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) -#endif - -#if defined(JSON_HEDLEY_INTEL_VERSION) - #undef JSON_HEDLEY_INTEL_VERSION -#endif -#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) - #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) -#elif defined(__INTEL_COMPILER) && !defined(__ICL) - #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) -#endif - -#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) - #undef JSON_HEDLEY_INTEL_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_INTEL_VERSION) - #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_INTEL_CL_VERSION) - #undef JSON_HEDLEY_INTEL_CL_VERSION -#endif -#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) - #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) -#endif - -#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) - #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_INTEL_CL_VERSION) - #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_PGI_VERSION) - #undef JSON_HEDLEY_PGI_VERSION -#endif -#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) - #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) -#endif - -#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) - #undef JSON_HEDLEY_PGI_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_PGI_VERSION) - #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_SUNPRO_VERSION) - #undef JSON_HEDLEY_SUNPRO_VERSION -#endif -#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) -#elif defined(__SUNPRO_C) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) -#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) -#elif defined(__SUNPRO_CC) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) -#endif - -#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) - #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_SUNPRO_VERSION) - #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) - #undef JSON_HEDLEY_EMSCRIPTEN_VERSION -#endif -#if defined(__EMSCRIPTEN__) - #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) -#endif - -#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) - #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) - #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_ARM_VERSION) - #undef JSON_HEDLEY_ARM_VERSION -#endif -#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) - #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) -#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) - #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) -#endif - -#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) - #undef JSON_HEDLEY_ARM_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_ARM_VERSION) - #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_IBM_VERSION) - #undef JSON_HEDLEY_IBM_VERSION -#endif -#if defined(__ibmxl__) - #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) -#elif defined(__xlC__) && defined(__xlC_ver__) - #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) -#elif defined(__xlC__) - #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) -#endif - -#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) - #undef JSON_HEDLEY_IBM_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_IBM_VERSION) - #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_VERSION) - #undef JSON_HEDLEY_TI_VERSION -#endif -#if \ - defined(__TI_COMPILER_VERSION__) && \ - ( \ - defined(__TMS470__) || defined(__TI_ARM__) || \ - defined(__MSP430__) || \ - defined(__TMS320C2000__) \ - ) -#if (__TI_COMPILER_VERSION__ >= 16000000) - #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif -#endif - -#if defined(JSON_HEDLEY_TI_VERSION_CHECK) - #undef JSON_HEDLEY_TI_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_VERSION) - #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL2000_VERSION) - #undef JSON_HEDLEY_TI_CL2000_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) - #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL2000_VERSION) - #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL430_VERSION) - #undef JSON_HEDLEY_TI_CL430_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) - #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL430_VERSION) - #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) - #undef JSON_HEDLEY_TI_ARMCL_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) - #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) - #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) - #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL6X_VERSION) - #undef JSON_HEDLEY_TI_CL6X_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) - #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL6X_VERSION) - #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL7X_VERSION) - #undef JSON_HEDLEY_TI_CL7X_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) - #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL7X_VERSION) - #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) - #undef JSON_HEDLEY_TI_CLPRU_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) - #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) - #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_CRAY_VERSION) - #undef JSON_HEDLEY_CRAY_VERSION -#endif -#if defined(_CRAYC) - #if defined(_RELEASE_PATCHLEVEL) - #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) - #else - #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) - #endif -#endif - -#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) - #undef JSON_HEDLEY_CRAY_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_CRAY_VERSION) - #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_IAR_VERSION) - #undef JSON_HEDLEY_IAR_VERSION -#endif -#if defined(__IAR_SYSTEMS_ICC__) - #if __VER__ > 1000 - #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) - #else - #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) - #endif -#endif - -#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) - #undef JSON_HEDLEY_IAR_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_IAR_VERSION) - #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TINYC_VERSION) - #undef JSON_HEDLEY_TINYC_VERSION -#endif -#if defined(__TINYC__) - #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) -#endif - -#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) - #undef JSON_HEDLEY_TINYC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TINYC_VERSION) - #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_DMC_VERSION) - #undef JSON_HEDLEY_DMC_VERSION -#endif -#if defined(__DMC__) - #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) -#endif - -#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) - #undef JSON_HEDLEY_DMC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_DMC_VERSION) - #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_COMPCERT_VERSION) - #undef JSON_HEDLEY_COMPCERT_VERSION -#endif -#if defined(__COMPCERT_VERSION__) - #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) -#endif - -#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) - #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_COMPCERT_VERSION) - #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_PELLES_VERSION) - #undef JSON_HEDLEY_PELLES_VERSION -#endif -#if defined(__POCC__) - #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) -#endif - -#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) - #undef JSON_HEDLEY_PELLES_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_PELLES_VERSION) - #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_MCST_LCC_VERSION) - #undef JSON_HEDLEY_MCST_LCC_VERSION -#endif -#if defined(__LCC__) && defined(__LCC_MINOR__) - #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) -#endif - -#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) - #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_MCST_LCC_VERSION) - #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_GCC_VERSION) - #undef JSON_HEDLEY_GCC_VERSION -#endif -#if \ - defined(JSON_HEDLEY_GNUC_VERSION) && \ - !defined(__clang__) && \ - !defined(JSON_HEDLEY_INTEL_VERSION) && \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_ARM_VERSION) && \ - !defined(JSON_HEDLEY_CRAY_VERSION) && \ - !defined(JSON_HEDLEY_TI_VERSION) && \ - !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ - !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ - !defined(__COMPCERT__) && \ - !defined(JSON_HEDLEY_MCST_LCC_VERSION) - #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION -#endif - -#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) - #undef JSON_HEDLEY_GCC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_GCC_VERSION) - #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_HAS_ATTRIBUTE -#endif -#if \ - defined(__has_attribute) && \ - ( \ - (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ - ) -# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) -#else -# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE -#endif -#if defined(__has_attribute) - #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) -#else - #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE -#endif -#if defined(__has_attribute) - #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) -#else - #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE -#endif -#if \ - defined(__has_cpp_attribute) && \ - defined(__cplusplus) && \ - (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) -#else - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) -#endif - -#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) - #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS -#endif -#if !defined(__cplusplus) || !defined(__has_cpp_attribute) - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) -#elif \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_IAR_VERSION) && \ - (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ - (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) -#else - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE -#endif -#if defined(__has_cpp_attribute) && defined(__cplusplus) - #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) -#else - #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE -#endif -#if defined(__has_cpp_attribute) && defined(__cplusplus) - #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) -#else - #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_BUILTIN) - #undef JSON_HEDLEY_HAS_BUILTIN -#endif -#if defined(__has_builtin) - #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) -#else - #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) - #undef JSON_HEDLEY_GNUC_HAS_BUILTIN -#endif -#if defined(__has_builtin) - #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) -#else - #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) - #undef JSON_HEDLEY_GCC_HAS_BUILTIN -#endif -#if defined(__has_builtin) - #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) -#else - #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_FEATURE) - #undef JSON_HEDLEY_HAS_FEATURE -#endif -#if defined(__has_feature) - #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) -#else - #define JSON_HEDLEY_HAS_FEATURE(feature) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) - #undef JSON_HEDLEY_GNUC_HAS_FEATURE -#endif -#if defined(__has_feature) - #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) -#else - #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) - #undef JSON_HEDLEY_GCC_HAS_FEATURE -#endif -#if defined(__has_feature) - #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) -#else - #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_EXTENSION) - #undef JSON_HEDLEY_HAS_EXTENSION -#endif -#if defined(__has_extension) - #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) -#else - #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) - #undef JSON_HEDLEY_GNUC_HAS_EXTENSION -#endif -#if defined(__has_extension) - #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) -#else - #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) - #undef JSON_HEDLEY_GCC_HAS_EXTENSION -#endif -#if defined(__has_extension) - #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) -#else - #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE -#endif -#if defined(__has_declspec_attribute) - #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) -#else - #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE -#endif -#if defined(__has_declspec_attribute) - #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) -#else - #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE -#endif -#if defined(__has_declspec_attribute) - #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) -#else - #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_WARNING) - #undef JSON_HEDLEY_HAS_WARNING -#endif -#if defined(__has_warning) - #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) -#else - #define JSON_HEDLEY_HAS_WARNING(warning) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) - #undef JSON_HEDLEY_GNUC_HAS_WARNING -#endif -#if defined(__has_warning) - #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) -#else - #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_WARNING) - #undef JSON_HEDLEY_GCC_HAS_WARNING -#endif -#if defined(__has_warning) - #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) -#else - #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if \ - (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ - defined(__clang__) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ - (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) - #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_PRAGMA(value) __pragma(value) -#else - #define JSON_HEDLEY_PRAGMA(value) -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) - #undef JSON_HEDLEY_DIAGNOSTIC_PUSH -#endif -#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) - #undef JSON_HEDLEY_DIAGNOSTIC_POP -#endif -#if defined(__clang__) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) - #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) -#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") -#else - #define JSON_HEDLEY_DIAGNOSTIC_PUSH - #define JSON_HEDLEY_DIAGNOSTIC_POP -#endif - -/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for - HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ -#endif -#if defined(__cplusplus) -# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") -# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") -# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") -# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ - _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ - _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ - xpr \ - JSON_HEDLEY_DIAGNOSTIC_POP -# else -# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ - _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ - xpr \ - JSON_HEDLEY_DIAGNOSTIC_POP -# endif -# else -# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ - xpr \ - JSON_HEDLEY_DIAGNOSTIC_POP -# endif -# endif -#endif -#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x -#endif - -#if defined(JSON_HEDLEY_CONST_CAST) - #undef JSON_HEDLEY_CONST_CAST -#endif -#if defined(__cplusplus) -# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) -#elif \ - JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) -# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ - ((T) (expr)); \ - JSON_HEDLEY_DIAGNOSTIC_POP \ - })) -#else -# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) -#endif - -#if defined(JSON_HEDLEY_REINTERPRET_CAST) - #undef JSON_HEDLEY_REINTERPRET_CAST -#endif -#if defined(__cplusplus) - #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) -#else - #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) -#endif - -#if defined(JSON_HEDLEY_STATIC_CAST) - #undef JSON_HEDLEY_STATIC_CAST -#endif -#if defined(__cplusplus) - #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) -#else - #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) -#endif - -#if defined(JSON_HEDLEY_CPP_CAST) - #undef JSON_HEDLEY_CPP_CAST -#endif -#if defined(__cplusplus) -# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") -# define JSON_HEDLEY_CPP_CAST(T, expr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ - ((T) (expr)) \ - JSON_HEDLEY_DIAGNOSTIC_POP -# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) -# define JSON_HEDLEY_CPP_CAST(T, expr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("diag_suppress=Pe137") \ - JSON_HEDLEY_DIAGNOSTIC_POP -# else -# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) -# endif -#else -# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") -#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") -#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) -#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") -#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") -#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") -#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") -#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") -#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") -#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) -#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION -#endif - -#if defined(JSON_HEDLEY_DEPRECATED) - #undef JSON_HEDLEY_DEPRECATED -#endif -#if defined(JSON_HEDLEY_DEPRECATED_FOR) - #undef JSON_HEDLEY_DEPRECATED_FOR -#endif -#if \ - JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) -#elif \ - (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) -#elif defined(__cplusplus) && (__cplusplus >= 201402L) - #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) -#elif \ - JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) - #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") -#else - #define JSON_HEDLEY_DEPRECATED(since) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) -#endif - -#if defined(JSON_HEDLEY_UNAVAILABLE) - #undef JSON_HEDLEY_UNAVAILABLE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) -#else - #define JSON_HEDLEY_UNAVAILABLE(available_since) -#endif - -#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) - #undef JSON_HEDLEY_WARN_UNUSED_RESULT -#endif -#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) - #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) -#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) - #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) -#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) - #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) -#elif defined(_Check_return_) /* SAL */ - #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ -#else - #define JSON_HEDLEY_WARN_UNUSED_RESULT - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) -#endif - -#if defined(JSON_HEDLEY_SENTINEL) - #undef JSON_HEDLEY_SENTINEL -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) -#else - #define JSON_HEDLEY_SENTINEL(position) -#endif - -#if defined(JSON_HEDLEY_NO_RETURN) - #undef JSON_HEDLEY_NO_RETURN -#endif -#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_NO_RETURN __noreturn -#elif \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) -#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L - #define JSON_HEDLEY_NO_RETURN _Noreturn -#elif defined(__cplusplus) && (__cplusplus >= 201103L) - #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) -#elif \ - JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) - #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) - #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) -#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) - #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") -#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) - #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) - #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) -#else - #define JSON_HEDLEY_NO_RETURN -#endif - -#if defined(JSON_HEDLEY_NO_ESCAPE) - #undef JSON_HEDLEY_NO_ESCAPE -#endif -#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) - #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) -#else - #define JSON_HEDLEY_NO_ESCAPE -#endif - -#if defined(JSON_HEDLEY_UNREACHABLE) - #undef JSON_HEDLEY_UNREACHABLE -#endif -#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) - #undef JSON_HEDLEY_UNREACHABLE_RETURN -#endif -#if defined(JSON_HEDLEY_ASSUME) - #undef JSON_HEDLEY_ASSUME -#endif -#if \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_ASSUME(expr) __assume(expr) -#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) - #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) -#elif \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) - #if defined(__cplusplus) - #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) - #else - #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) - #endif -#endif -#if \ - (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() -#elif defined(JSON_HEDLEY_ASSUME) - #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) -#endif -#if !defined(JSON_HEDLEY_ASSUME) - #if defined(JSON_HEDLEY_UNREACHABLE) - #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) - #else - #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) - #endif -#endif -#if defined(JSON_HEDLEY_UNREACHABLE) - #if \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) - #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) - #else - #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() - #endif -#else - #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) -#endif -#if !defined(JSON_HEDLEY_UNREACHABLE) - #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) -#endif - -JSON_HEDLEY_DIAGNOSTIC_PUSH -#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") - #pragma clang diagnostic ignored "-Wpedantic" -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) - #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" -#endif -#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) - #if defined(__clang__) - #pragma clang diagnostic ignored "-Wvariadic-macros" - #elif defined(JSON_HEDLEY_GCC_VERSION) - #pragma GCC diagnostic ignored "-Wvariadic-macros" - #endif -#endif -#if defined(JSON_HEDLEY_NON_NULL) - #undef JSON_HEDLEY_NON_NULL -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) - #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) -#else - #define JSON_HEDLEY_NON_NULL(...) -#endif -JSON_HEDLEY_DIAGNOSTIC_POP - -#if defined(JSON_HEDLEY_PRINTF_FORMAT) - #undef JSON_HEDLEY_PRINTF_FORMAT -#endif -#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) -#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) -#elif \ - JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) -#else - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) -#endif - -#if defined(JSON_HEDLEY_CONSTEXPR) - #undef JSON_HEDLEY_CONSTEXPR -#endif -#if defined(__cplusplus) - #if __cplusplus >= 201103L - #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) - #endif -#endif -#if !defined(JSON_HEDLEY_CONSTEXPR) - #define JSON_HEDLEY_CONSTEXPR -#endif - -#if defined(JSON_HEDLEY_PREDICT) - #undef JSON_HEDLEY_PREDICT -#endif -#if defined(JSON_HEDLEY_LIKELY) - #undef JSON_HEDLEY_LIKELY -#endif -#if defined(JSON_HEDLEY_UNLIKELY) - #undef JSON_HEDLEY_UNLIKELY -#endif -#if defined(JSON_HEDLEY_UNPREDICTABLE) - #undef JSON_HEDLEY_UNPREDICTABLE -#endif -#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) - #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) -#endif -#if \ - (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) -# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) -# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) -# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) -# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) -# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) -#elif \ - (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) -# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ - (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) -# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ - (__extension__ ({ \ - double hedley_probability_ = (probability); \ - ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ - })) -# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ - (__extension__ ({ \ - double hedley_probability_ = (probability); \ - ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ - })) -# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) -# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) -#else -# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) -# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) -# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) -# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) -# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) -#endif -#if !defined(JSON_HEDLEY_UNPREDICTABLE) - #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) -#endif - -#if defined(JSON_HEDLEY_MALLOC) - #undef JSON_HEDLEY_MALLOC -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) - #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_MALLOC __declspec(restrict) -#else - #define JSON_HEDLEY_MALLOC -#endif - -#if defined(JSON_HEDLEY_PURE) - #undef JSON_HEDLEY_PURE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) -# define JSON_HEDLEY_PURE __attribute__((__pure__)) -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) -# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") -#elif defined(__cplusplus) && \ - ( \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ - ) -# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") -#else -# define JSON_HEDLEY_PURE -#endif - -#if defined(JSON_HEDLEY_CONST) - #undef JSON_HEDLEY_CONST -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_CONST __attribute__((__const__)) -#elif \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) - #define JSON_HEDLEY_CONST _Pragma("no_side_effect") -#else - #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE -#endif - -#if defined(JSON_HEDLEY_RESTRICT) - #undef JSON_HEDLEY_RESTRICT -#endif -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) - #define JSON_HEDLEY_RESTRICT restrict -#elif \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ - defined(__clang__) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_RESTRICT __restrict -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) - #define JSON_HEDLEY_RESTRICT _Restrict -#else - #define JSON_HEDLEY_RESTRICT -#endif - -#if defined(JSON_HEDLEY_INLINE) - #undef JSON_HEDLEY_INLINE -#endif -#if \ - (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ - (defined(__cplusplus) && (__cplusplus >= 199711L)) - #define JSON_HEDLEY_INLINE inline -#elif \ - defined(JSON_HEDLEY_GCC_VERSION) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) - #define JSON_HEDLEY_INLINE __inline__ -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_INLINE __inline -#else - #define JSON_HEDLEY_INLINE -#endif - -#if defined(JSON_HEDLEY_ALWAYS_INLINE) - #undef JSON_HEDLEY_ALWAYS_INLINE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) -# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) -# define JSON_HEDLEY_ALWAYS_INLINE __forceinline -#elif defined(__cplusplus) && \ - ( \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ - ) -# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) -# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") -#else -# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE -#endif - -#if defined(JSON_HEDLEY_NEVER_INLINE) - #undef JSON_HEDLEY_NEVER_INLINE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) - #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) - #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") -#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) - #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") -#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) - #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) - #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) -#else - #define JSON_HEDLEY_NEVER_INLINE -#endif - -#if defined(JSON_HEDLEY_PRIVATE) - #undef JSON_HEDLEY_PRIVATE -#endif -#if defined(JSON_HEDLEY_PUBLIC) - #undef JSON_HEDLEY_PUBLIC -#endif -#if defined(JSON_HEDLEY_IMPORT) - #undef JSON_HEDLEY_IMPORT -#endif -#if defined(_WIN32) || defined(__CYGWIN__) -# define JSON_HEDLEY_PRIVATE -# define JSON_HEDLEY_PUBLIC __declspec(dllexport) -# define JSON_HEDLEY_IMPORT __declspec(dllimport) -#else -# if \ - JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ - ( \ - defined(__TI_EABI__) && \ - ( \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ - ) \ - ) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) -# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) -# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) -# else -# define JSON_HEDLEY_PRIVATE -# define JSON_HEDLEY_PUBLIC -# endif -# define JSON_HEDLEY_IMPORT extern -#endif - -#if defined(JSON_HEDLEY_NO_THROW) - #undef JSON_HEDLEY_NO_THROW -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) - #define JSON_HEDLEY_NO_THROW __declspec(nothrow) -#else - #define JSON_HEDLEY_NO_THROW -#endif - -#if defined(JSON_HEDLEY_FALL_THROUGH) - #undef JSON_HEDLEY_FALL_THROUGH -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) -#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) - #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) -#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) - #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) -#elif defined(__fallthrough) /* SAL */ - #define JSON_HEDLEY_FALL_THROUGH __fallthrough -#else - #define JSON_HEDLEY_FALL_THROUGH -#endif - -#if defined(JSON_HEDLEY_RETURNS_NON_NULL) - #undef JSON_HEDLEY_RETURNS_NON_NULL -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) -#elif defined(_Ret_notnull_) /* SAL */ - #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ -#else - #define JSON_HEDLEY_RETURNS_NON_NULL -#endif - -#if defined(JSON_HEDLEY_ARRAY_PARAM) - #undef JSON_HEDLEY_ARRAY_PARAM -#endif -#if \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ - !defined(__STDC_NO_VLA__) && \ - !defined(__cplusplus) && \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_TINYC_VERSION) - #define JSON_HEDLEY_ARRAY_PARAM(name) (name) -#else - #define JSON_HEDLEY_ARRAY_PARAM(name) -#endif - -#if defined(JSON_HEDLEY_IS_CONSTANT) - #undef JSON_HEDLEY_IS_CONSTANT -#endif -#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) - #undef JSON_HEDLEY_REQUIRE_CONSTEXPR -#endif -/* JSON_HEDLEY_IS_CONSTEXPR_ is for - HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ -#if defined(JSON_HEDLEY_IS_CONSTEXPR_) - #undef JSON_HEDLEY_IS_CONSTEXPR_ -#endif -#if \ - JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) -#endif -#if !defined(__cplusplus) -# if \ - JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) -#if defined(__INTPTR_TYPE__) - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) -#else - #include - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) -#endif -# elif \ - ( \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ - !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_IAR_VERSION)) || \ - (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) -#if defined(__INTPTR_TYPE__) - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) -#else - #include - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) -#endif -# elif \ - defined(JSON_HEDLEY_GCC_VERSION) || \ - defined(JSON_HEDLEY_INTEL_VERSION) || \ - defined(JSON_HEDLEY_TINYC_VERSION) || \ - defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ - defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ - defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ - defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ - defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ - defined(__clang__) -# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ - sizeof(void) != \ - sizeof(*( \ - 1 ? \ - ((void*) ((expr) * 0L) ) : \ -((struct { char v[sizeof(void) * 2]; } *) 1) \ - ) \ - ) \ - ) -# endif -#endif -#if defined(JSON_HEDLEY_IS_CONSTEXPR_) - #if !defined(JSON_HEDLEY_IS_CONSTANT) - #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) - #endif - #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) -#else - #if !defined(JSON_HEDLEY_IS_CONSTANT) - #define JSON_HEDLEY_IS_CONSTANT(expr) (0) - #endif - #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) -#endif - -#if defined(JSON_HEDLEY_BEGIN_C_DECLS) - #undef JSON_HEDLEY_BEGIN_C_DECLS -#endif -#if defined(JSON_HEDLEY_END_C_DECLS) - #undef JSON_HEDLEY_END_C_DECLS -#endif -#if defined(JSON_HEDLEY_C_DECL) - #undef JSON_HEDLEY_C_DECL -#endif -#if defined(__cplusplus) - #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { - #define JSON_HEDLEY_END_C_DECLS } - #define JSON_HEDLEY_C_DECL extern "C" -#else - #define JSON_HEDLEY_BEGIN_C_DECLS - #define JSON_HEDLEY_END_C_DECLS - #define JSON_HEDLEY_C_DECL -#endif - -#if defined(JSON_HEDLEY_STATIC_ASSERT) - #undef JSON_HEDLEY_STATIC_ASSERT -#endif -#if \ - !defined(__cplusplus) && ( \ - (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ - (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - defined(_Static_assert) \ - ) -# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) -#elif \ - (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ - JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) -# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) -#else -# define JSON_HEDLEY_STATIC_ASSERT(expr, message) -#endif - -#if defined(JSON_HEDLEY_NULL) - #undef JSON_HEDLEY_NULL -#endif -#if defined(__cplusplus) - #if __cplusplus >= 201103L - #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) - #elif defined(NULL) - #define JSON_HEDLEY_NULL NULL - #else - #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) - #endif -#elif defined(NULL) - #define JSON_HEDLEY_NULL NULL -#else - #define JSON_HEDLEY_NULL ((void*) 0) -#endif - -#if defined(JSON_HEDLEY_MESSAGE) - #undef JSON_HEDLEY_MESSAGE -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") -# define JSON_HEDLEY_MESSAGE(msg) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ - JSON_HEDLEY_PRAGMA(message msg) \ - JSON_HEDLEY_DIAGNOSTIC_POP -#elif \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) -#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) -#else -# define JSON_HEDLEY_MESSAGE(msg) -#endif - -#if defined(JSON_HEDLEY_WARNING) - #undef JSON_HEDLEY_WARNING -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") -# define JSON_HEDLEY_WARNING(msg) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ - JSON_HEDLEY_PRAGMA(clang warning msg) \ - JSON_HEDLEY_DIAGNOSTIC_POP -#elif \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) -# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) -# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) -#else -# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) -#endif - -#if defined(JSON_HEDLEY_REQUIRE) - #undef JSON_HEDLEY_REQUIRE -#endif -#if defined(JSON_HEDLEY_REQUIRE_MSG) - #undef JSON_HEDLEY_REQUIRE_MSG -#endif -#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) -# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") -# define JSON_HEDLEY_REQUIRE(expr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ - __attribute__((diagnose_if(!(expr), #expr, "error"))) \ - JSON_HEDLEY_DIAGNOSTIC_POP -# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ - __attribute__((diagnose_if(!(expr), msg, "error"))) \ - JSON_HEDLEY_DIAGNOSTIC_POP -# else -# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) -# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) -# endif -#else -# define JSON_HEDLEY_REQUIRE(expr) -# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) -#endif - -#if defined(JSON_HEDLEY_FLAGS) - #undef JSON_HEDLEY_FLAGS -#endif -#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) - #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) -#else - #define JSON_HEDLEY_FLAGS -#endif - -#if defined(JSON_HEDLEY_FLAGS_CAST) - #undef JSON_HEDLEY_FLAGS_CAST -#endif -#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) -# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("warning(disable:188)") \ - ((T) (expr)); \ - JSON_HEDLEY_DIAGNOSTIC_POP \ - })) -#else -# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) -#endif - -#if defined(JSON_HEDLEY_EMPTY_BASES) - #undef JSON_HEDLEY_EMPTY_BASES -#endif -#if \ - (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) -#else - #define JSON_HEDLEY_EMPTY_BASES -#endif - -/* Remaining macros are deprecated. */ - -#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) - #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK -#endif -#if defined(__clang__) - #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) -#else - #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE -#endif -#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) - -#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE -#endif -#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) - -#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) - #undef JSON_HEDLEY_CLANG_HAS_BUILTIN -#endif -#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) - -#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) - #undef JSON_HEDLEY_CLANG_HAS_FEATURE -#endif -#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) - -#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) - #undef JSON_HEDLEY_CLANG_HAS_EXTENSION -#endif -#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) - -#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE -#endif -#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) - -#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) - #undef JSON_HEDLEY_CLANG_HAS_WARNING -#endif -#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) - -#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ - - -// This file contains all internal macro definitions (except those affecting ABI) -// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them - -// #include - - -// exclude unsupported compilers -#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) - #if defined(__clang__) - #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 - #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" - #endif - #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) - #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 - #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" - #endif - #endif -#endif - -// C++ language standard detection -// if the user manually specified the used c++ version this is skipped -#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) - #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) - #define JSON_HAS_CPP_20 - #define JSON_HAS_CPP_17 - #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 - #define JSON_HAS_CPP_17 - #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) - #define JSON_HAS_CPP_14 - #endif - // the cpp 11 flag is always specified because it is the minimal required version - #define JSON_HAS_CPP_11 -#endif - -#ifdef __has_include - #if __has_include() - #include - #endif -#endif - -#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) - #ifdef JSON_HAS_CPP_17 - #if defined(__cpp_lib_filesystem) - #define JSON_HAS_FILESYSTEM 1 - #elif defined(__cpp_lib_experimental_filesystem) - #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 - #elif !defined(__has_include) - #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 - #elif __has_include() - #define JSON_HAS_FILESYSTEM 1 - #elif __has_include() - #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 - #endif - - // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ - #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support - #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support - #if defined(__clang_major__) && __clang_major__ < 7 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support - #if defined(_MSC_VER) && _MSC_VER < 1914 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before iOS 13 - #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before macOS Catalina - #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - #endif -#endif - -#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 -#endif - -#ifndef JSON_HAS_FILESYSTEM - #define JSON_HAS_FILESYSTEM 0 -#endif - -#ifndef JSON_HAS_THREE_WAY_COMPARISON - #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ - && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L - #define JSON_HAS_THREE_WAY_COMPARISON 1 - #else - #define JSON_HAS_THREE_WAY_COMPARISON 0 - #endif -#endif - -#ifndef JSON_HAS_RANGES - // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error - #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 - #define JSON_HAS_RANGES 0 - #elif defined(__cpp_lib_ranges) - #define JSON_HAS_RANGES 1 - #else - #define JSON_HAS_RANGES 0 - #endif -#endif - -#ifndef JSON_HAS_STATIC_RTTI - #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0 - #define JSON_HAS_STATIC_RTTI 1 - #else - #define JSON_HAS_STATIC_RTTI 0 - #endif -#endif - -#ifdef JSON_HAS_CPP_17 - #define JSON_INLINE_VARIABLE inline -#else - #define JSON_INLINE_VARIABLE -#endif - -#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) - #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] -#else - #define JSON_NO_UNIQUE_ADDRESS -#endif - -// disable documentation warnings on clang -#if defined(__clang__) - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdocumentation" - #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" -#endif - -// allow disabling exceptions -#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) - #define JSON_THROW(exception) throw exception - #define JSON_TRY try - #define JSON_CATCH(exception) catch(exception) - #define JSON_INTERNAL_CATCH(exception) catch(exception) -#else - #include - #define JSON_THROW(exception) std::abort() - #define JSON_TRY if(true) - #define JSON_CATCH(exception) if(false) - #define JSON_INTERNAL_CATCH(exception) if(false) -#endif - -// override exception macros -#if defined(JSON_THROW_USER) - #undef JSON_THROW - #define JSON_THROW JSON_THROW_USER -#endif -#if defined(JSON_TRY_USER) - #undef JSON_TRY - #define JSON_TRY JSON_TRY_USER -#endif -#if defined(JSON_CATCH_USER) - #undef JSON_CATCH - #define JSON_CATCH JSON_CATCH_USER - #undef JSON_INTERNAL_CATCH - #define JSON_INTERNAL_CATCH JSON_CATCH_USER -#endif -#if defined(JSON_INTERNAL_CATCH_USER) - #undef JSON_INTERNAL_CATCH - #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER -#endif - -// allow overriding assert -#if !defined(JSON_ASSERT) - #include // assert - #define JSON_ASSERT(x) assert(x) -#endif - -// allow to access some private functions (needed by the test suite) -#if defined(JSON_TESTS_PRIVATE) - #define JSON_PRIVATE_UNLESS_TESTED public -#else - #define JSON_PRIVATE_UNLESS_TESTED private -#endif - -/*! -@brief macro to briefly define a mapping between an enum and JSON -@def NLOHMANN_JSON_SERIALIZE_ENUM -@since version 3.4.0 -*/ -#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ - template \ - inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ - { \ - static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ - static const std::pair m[] = __VA_ARGS__; \ - auto it = std::find_if(std::begin(m), std::end(m), \ - [e](const std::pair& ej_pair) -> bool \ - { \ - return ej_pair.first == e; \ - }); \ - j = ((it != std::end(m)) ? it : std::begin(m))->second; \ - } \ - template \ - inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ - { \ - static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ - static const std::pair m[] = __VA_ARGS__; \ - auto it = std::find_if(std::begin(m), std::end(m), \ - [&j](const std::pair& ej_pair) -> bool \ - { \ - return ej_pair.second == j; \ - }); \ - e = ((it != std::end(m)) ? it : std::begin(m))->first; \ - } - -// Ugly macros to avoid uglier copy-paste when specializing basic_json. They -// may be removed in the future once the class is split. - -#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ - template class ObjectType, \ - template class ArrayType, \ - class StringType, class BooleanType, class NumberIntegerType, \ - class NumberUnsignedType, class NumberFloatType, \ - template class AllocatorType, \ - template class JSONSerializer, \ - class BinaryType, \ - class CustomBaseClass> - -#define NLOHMANN_BASIC_JSON_TPL \ - basic_json - -// Macros to simplify conversion from/to types - -#define NLOHMANN_JSON_EXPAND( x ) x -#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME -#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ - NLOHMANN_JSON_PASTE64, \ - NLOHMANN_JSON_PASTE63, \ - NLOHMANN_JSON_PASTE62, \ - NLOHMANN_JSON_PASTE61, \ - NLOHMANN_JSON_PASTE60, \ - NLOHMANN_JSON_PASTE59, \ - NLOHMANN_JSON_PASTE58, \ - NLOHMANN_JSON_PASTE57, \ - NLOHMANN_JSON_PASTE56, \ - NLOHMANN_JSON_PASTE55, \ - NLOHMANN_JSON_PASTE54, \ - NLOHMANN_JSON_PASTE53, \ - NLOHMANN_JSON_PASTE52, \ - NLOHMANN_JSON_PASTE51, \ - NLOHMANN_JSON_PASTE50, \ - NLOHMANN_JSON_PASTE49, \ - NLOHMANN_JSON_PASTE48, \ - NLOHMANN_JSON_PASTE47, \ - NLOHMANN_JSON_PASTE46, \ - NLOHMANN_JSON_PASTE45, \ - NLOHMANN_JSON_PASTE44, \ - NLOHMANN_JSON_PASTE43, \ - NLOHMANN_JSON_PASTE42, \ - NLOHMANN_JSON_PASTE41, \ - NLOHMANN_JSON_PASTE40, \ - NLOHMANN_JSON_PASTE39, \ - NLOHMANN_JSON_PASTE38, \ - NLOHMANN_JSON_PASTE37, \ - NLOHMANN_JSON_PASTE36, \ - NLOHMANN_JSON_PASTE35, \ - NLOHMANN_JSON_PASTE34, \ - NLOHMANN_JSON_PASTE33, \ - NLOHMANN_JSON_PASTE32, \ - NLOHMANN_JSON_PASTE31, \ - NLOHMANN_JSON_PASTE30, \ - NLOHMANN_JSON_PASTE29, \ - NLOHMANN_JSON_PASTE28, \ - NLOHMANN_JSON_PASTE27, \ - NLOHMANN_JSON_PASTE26, \ - NLOHMANN_JSON_PASTE25, \ - NLOHMANN_JSON_PASTE24, \ - NLOHMANN_JSON_PASTE23, \ - NLOHMANN_JSON_PASTE22, \ - NLOHMANN_JSON_PASTE21, \ - NLOHMANN_JSON_PASTE20, \ - NLOHMANN_JSON_PASTE19, \ - NLOHMANN_JSON_PASTE18, \ - NLOHMANN_JSON_PASTE17, \ - NLOHMANN_JSON_PASTE16, \ - NLOHMANN_JSON_PASTE15, \ - NLOHMANN_JSON_PASTE14, \ - NLOHMANN_JSON_PASTE13, \ - NLOHMANN_JSON_PASTE12, \ - NLOHMANN_JSON_PASTE11, \ - NLOHMANN_JSON_PASTE10, \ - NLOHMANN_JSON_PASTE9, \ - NLOHMANN_JSON_PASTE8, \ - NLOHMANN_JSON_PASTE7, \ - NLOHMANN_JSON_PASTE6, \ - NLOHMANN_JSON_PASTE5, \ - NLOHMANN_JSON_PASTE4, \ - NLOHMANN_JSON_PASTE3, \ - NLOHMANN_JSON_PASTE2, \ - NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) -#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) -#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) -#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) -#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) -#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) -#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) -#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) -#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) -#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) -#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) -#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) -#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) -#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) -#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) -#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) -#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) -#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) -#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) -#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) -#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) -#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) -#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) -#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) -#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) -#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) -#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) -#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) -#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) -#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) -#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) -#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) -#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) -#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) -#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) -#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) -#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) -#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) -#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) -#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) -#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) -#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) -#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) -#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) -#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) -#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) -#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) -#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) -#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) -#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) -#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) -#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) -#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) -#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) -#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) -#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) -#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) -#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) -#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) -#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) -#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) -#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) -#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) -#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) - -#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; -#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); -#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); - -/*! -@brief macro -@def NLOHMANN_DEFINE_TYPE_INTRUSIVE -@since version 3.9.0 -*/ -#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } - -#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } - -#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE -@since version 3.9.0 -*/ -#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } - -#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } - -#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } - -// inspired from https://stackoverflow.com/a/26745591 -// allows to call any std function as if (e.g. with begin): -// using std::begin; begin(x); -// -// it allows using the detected idiom to retrieve the return type -// of such an expression -#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ - namespace detail { \ - using std::std_name; \ - \ - template \ - using result_of_##std_name = decltype(std_name(std::declval()...)); \ - } \ - \ - namespace detail2 { \ - struct std_name##_tag \ - { \ - }; \ - \ - template \ - std_name##_tag std_name(T&&...); \ - \ - template \ - using result_of_##std_name = decltype(std_name(std::declval()...)); \ - \ - template \ - struct would_call_std_##std_name \ - { \ - static constexpr auto const value = ::nlohmann::detail:: \ - is_detected_exact::value; \ - }; \ - } /* namespace detail2 */ \ - \ - template \ - struct would_call_std_##std_name : detail2::would_call_std_##std_name \ - { \ - } - -#ifndef JSON_USE_IMPLICIT_CONVERSIONS - #define JSON_USE_IMPLICIT_CONVERSIONS 1 -#endif - -#if JSON_USE_IMPLICIT_CONVERSIONS - #define JSON_EXPLICIT -#else - #define JSON_EXPLICIT explicit -#endif - -#ifndef JSON_DISABLE_ENUM_SERIALIZATION - #define JSON_DISABLE_ENUM_SERIALIZATION 0 -#endif - -#ifndef JSON_USE_GLOBAL_UDLS - #define JSON_USE_GLOBAL_UDLS 1 -#endif - -#if JSON_HAS_THREE_WAY_COMPARISON - #include // partial_ordering -#endif - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -/////////////////////////// -// JSON type enumeration // -/////////////////////////// - -/*! -@brief the JSON type enumeration - -This enumeration collects the different JSON types. It is internally used to -distinguish the stored values, and the functions @ref basic_json::is_null(), -@ref basic_json::is_object(), @ref basic_json::is_array(), -@ref basic_json::is_string(), @ref basic_json::is_boolean(), -@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), -@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), -@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and -@ref basic_json::is_structured() rely on it. - -@note There are three enumeration entries (number_integer, number_unsigned, and -number_float), because the library distinguishes these three types for numbers: -@ref basic_json::number_unsigned_t is used for unsigned integers, -@ref basic_json::number_integer_t is used for signed integers, and -@ref basic_json::number_float_t is used for floating-point numbers or to -approximate integers which do not fit in the limits of their respective type. - -@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON -value with the default value for a given type - -@since version 1.0.0 -*/ -enum class value_t : std::uint8_t -{ - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - number_integer, ///< number value (signed integer) - number_unsigned, ///< number value (unsigned integer) - number_float, ///< number value (floating-point) - binary, ///< binary array (ordered collection of bytes) - discarded ///< discarded by the parser callback function -}; - -/*! -@brief comparison operator for JSON types - -Returns an ordering that is similar to Python: -- order: null < boolean < number < object < array < string < binary -- furthermore, each type is not smaller than itself -- discarded values are not comparable -- binary is represented as a b"" string in python and directly comparable to a - string; however, making a binary array directly comparable with a string would - be surprising behavior in a JSON file. - -@since version 1.0.0 -*/ -#if JSON_HAS_THREE_WAY_COMPARISON - inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* -#else - inline bool operator<(const value_t lhs, const value_t rhs) noexcept -#endif -{ - static constexpr std::array order = {{ - 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, - 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, - 6 /* binary */ - } - }; - - const auto l_index = static_cast(lhs); - const auto r_index = static_cast(rhs); -#if JSON_HAS_THREE_WAY_COMPARISON - if (l_index < order.size() && r_index < order.size()) - { - return order[l_index] <=> order[r_index]; // *NOPAD* - } - return std::partial_ordering::unordered; -#else - return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; -#endif -} - -// GCC selects the built-in operator< over an operator rewritten from -// a user-defined spaceship operator -// Clang, MSVC, and ICC select the rewritten candidate -// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) -#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) -inline bool operator<(const value_t lhs, const value_t rhs) noexcept -{ - return std::is_lt(lhs <=> rhs); // *NOPAD* -} -#endif - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -/*! -@brief replace all occurrences of a substring by another string - -@param[in,out] s the string to manipulate; changed so that all - occurrences of @a f are replaced with @a t -@param[in] f the substring to replace with @a t -@param[in] t the string to replace @a f - -@pre The search string @a f must not be empty. **This precondition is -enforced with an assertion.** - -@since version 2.0.0 -*/ -template -inline void replace_substring(StringType& s, const StringType& f, - const StringType& t) -{ - JSON_ASSERT(!f.empty()); - for (auto pos = s.find(f); // find first occurrence of f - pos != StringType::npos; // make sure f was found - s.replace(pos, f.size(), t), // replace with t, and - pos = s.find(f, pos + t.size())) // find next occurrence of f - {} -} - -/*! - * @brief string escaping as described in RFC 6901 (Sect. 4) - * @param[in] s string to escape - * @return escaped string - * - * Note the order of escaping "~" to "~0" and "/" to "~1" is important. - */ -template -inline StringType escape(StringType s) -{ - replace_substring(s, StringType{"~"}, StringType{"~0"}); - replace_substring(s, StringType{"/"}, StringType{"~1"}); - return s; -} - -/*! - * @brief string unescaping as described in RFC 6901 (Sect. 4) - * @param[in] s string to unescape - * @return unescaped string - * - * Note the order of escaping "~1" to "/" and "~0" to "~" is important. - */ -template -static void unescape(StringType& s) -{ - replace_substring(s, StringType{"~1"}, StringType{"/"}); - replace_substring(s, StringType{"~0"}, StringType{"~"}); -} - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // size_t - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -/// struct to capture the start position of the current token -struct position_t -{ - /// the total number of characters read - std::size_t chars_read_total = 0; - /// the number of characters read in the current line - std::size_t chars_read_current_line = 0; - /// the number of lines read - std::size_t lines_read = 0; - - /// conversion to size_t to preserve SAX interface - constexpr operator size_t() const - { - return chars_read_total; - } -}; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-FileCopyrightText: 2018 The Abseil Authors -// SPDX-License-Identifier: MIT - - - -#include // array -#include // size_t -#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type -#include // index_sequence, make_index_sequence, index_sequence_for - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -template -using uncvref_t = typename std::remove_cv::type>::type; - -#ifdef JSON_HAS_CPP_14 - -// the following utilities are natively available in C++14 -using std::enable_if_t; -using std::index_sequence; -using std::make_index_sequence; -using std::index_sequence_for; - -#else - -// alias templates to reduce boilerplate -template -using enable_if_t = typename std::enable_if::type; - -// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h -// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. - -//// START OF CODE FROM GOOGLE ABSEIL - -// integer_sequence -// -// Class template representing a compile-time integer sequence. An instantiation -// of `integer_sequence` has a sequence of integers encoded in its -// type through its template arguments (which is a common need when -// working with C++11 variadic templates). `absl::integer_sequence` is designed -// to be a drop-in replacement for C++14's `std::integer_sequence`. -// -// Example: -// -// template< class T, T... Ints > -// void user_function(integer_sequence); -// -// int main() -// { -// // user_function's `T` will be deduced to `int` and `Ints...` -// // will be deduced to `0, 1, 2, 3, 4`. -// user_function(make_integer_sequence()); -// } -template -struct integer_sequence -{ - using value_type = T; - static constexpr std::size_t size() noexcept - { - return sizeof...(Ints); - } -}; - -// index_sequence -// -// A helper template for an `integer_sequence` of `size_t`, -// `absl::index_sequence` is designed to be a drop-in replacement for C++14's -// `std::index_sequence`. -template -using index_sequence = integer_sequence; - -namespace utility_internal -{ - -template -struct Extend; - -// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. -template -struct Extend, SeqSize, 0> -{ - using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; -}; - -template -struct Extend, SeqSize, 1> -{ - using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; -}; - -// Recursion helper for 'make_integer_sequence'. -// 'Gen::type' is an alias for 'integer_sequence'. -template -struct Gen -{ - using type = - typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; -}; - -template -struct Gen -{ - using type = integer_sequence; -}; - -} // namespace utility_internal - -// Compile-time sequences of integers - -// make_integer_sequence -// -// This template alias is equivalent to -// `integer_sequence`, and is designed to be a drop-in -// replacement for C++14's `std::make_integer_sequence`. -template -using make_integer_sequence = typename utility_internal::Gen::type; - -// make_index_sequence -// -// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, -// and is designed to be a drop-in replacement for C++14's -// `std::make_index_sequence`. -template -using make_index_sequence = make_integer_sequence; - -// index_sequence_for -// -// Converts a typename pack into an index sequence of the same length, and -// is designed to be a drop-in replacement for C++14's -// `std::index_sequence_for()` -template -using index_sequence_for = make_index_sequence; - -//// END OF CODE FROM GOOGLE ABSEIL - -#endif - -// dispatch utility (taken from ranges-v3) -template struct priority_tag : priority_tag < N - 1 > {}; -template<> struct priority_tag<0> {}; - -// taken from ranges-v3 -template -struct static_const -{ - static JSON_INLINE_VARIABLE constexpr T value{}; -}; - -#ifndef JSON_HAS_CPP_17 - template - constexpr T static_const::value; -#endif - -template -inline constexpr std::array make_array(Args&& ... args) -{ - return std::array {{static_cast(std::forward(args))...}}; -} - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // numeric_limits -#include // false_type, is_constructible, is_integral, is_same, true_type -#include // declval -#include // tuple -#include // char_traits - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // random_access_iterator_tag - -// #include - -// #include - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -template -struct iterator_types {}; - -template -struct iterator_types < - It, - void_t> -{ - using difference_type = typename It::difference_type; - using value_type = typename It::value_type; - using pointer = typename It::pointer; - using reference = typename It::reference; - using iterator_category = typename It::iterator_category; -}; - -// This is required as some compilers implement std::iterator_traits in a way that -// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. -template -struct iterator_traits -{ -}; - -template -struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> - : iterator_types -{ -}; - -template -struct iterator_traits::value>> -{ - using iterator_category = std::random_access_iterator_tag; - using value_type = T; - using difference_type = ptrdiff_t; - using pointer = T*; - using reference = T&; -}; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN - -NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); - -NLOHMANN_JSON_NAMESPACE_END - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN - -NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); - -NLOHMANN_JSON_NAMESPACE_END - -// #include - -// #include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT - -#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ - #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ - - #include // int64_t, uint64_t - #include // map - #include // allocator - #include // string - #include // vector - - // #include - - - /*! - @brief namespace for Niels Lohmann - @see https://github.com/nlohmann - @since version 1.0.0 - */ - NLOHMANN_JSON_NAMESPACE_BEGIN - - /*! - @brief default JSONSerializer template argument - - This serializer ignores the template arguments and uses ADL - ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) - for serialization. - */ - template - struct adl_serializer; - - /// a class to store JSON values - /// @sa https://json.nlohmann.me/api/basic_json/ - template class ObjectType = - std::map, - template class ArrayType = std::vector, - class StringType = std::string, class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template class AllocatorType = std::allocator, - template class JSONSerializer = - adl_serializer, - class BinaryType = std::vector, // cppcheck-suppress syntaxError - class CustomBaseClass = void> - class basic_json; - - /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document - /// @sa https://json.nlohmann.me/api/json_pointer/ - template - class json_pointer; - - /*! - @brief default specialization - @sa https://json.nlohmann.me/api/json/ - */ - using json = basic_json<>; - - /// @brief a minimal map-like container that preserves insertion order - /// @sa https://json.nlohmann.me/api/ordered_map/ - template - struct ordered_map; - - /// @brief specialization that maintains the insertion order of object keys - /// @sa https://json.nlohmann.me/api/ordered_json/ - using ordered_json = basic_json; - - NLOHMANN_JSON_NAMESPACE_END - -#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ - - -NLOHMANN_JSON_NAMESPACE_BEGIN -/*! -@brief detail namespace with internal helper functions - -This namespace collects functions that should not be exposed, -implementations of some @ref basic_json methods, and meta-programming helpers. - -@since version 2.1.0 -*/ -namespace detail -{ - -///////////// -// helpers // -///////////// - -// Note to maintainers: -// -// Every trait in this file expects a non CV-qualified type. -// The only exceptions are in the 'aliases for detected' section -// (i.e. those of the form: decltype(T::member_function(std::declval()))) -// -// In this case, T has to be properly CV-qualified to constraint the function arguments -// (e.g. to_json(BasicJsonType&, const T&)) - -template struct is_basic_json : std::false_type {}; - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -struct is_basic_json : std::true_type {}; - -// used by exceptions create() member functions -// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t -// false_type otherwise -template -struct is_basic_json_context : - std::integral_constant < bool, - is_basic_json::type>::type>::value - || std::is_same::value > -{}; - -////////////////////// -// json_ref helpers // -////////////////////// - -template -class json_ref; - -template -struct is_json_ref : std::false_type {}; - -template -struct is_json_ref> : std::true_type {}; - -////////////////////////// -// aliases for detected // -////////////////////////// - -template -using mapped_type_t = typename T::mapped_type; - -template -using key_type_t = typename T::key_type; - -template -using value_type_t = typename T::value_type; - -template -using difference_type_t = typename T::difference_type; - -template -using pointer_t = typename T::pointer; - -template -using reference_t = typename T::reference; - -template -using iterator_category_t = typename T::iterator_category; - -template -using to_json_function = decltype(T::to_json(std::declval()...)); - -template -using from_json_function = decltype(T::from_json(std::declval()...)); - -template -using get_template_function = decltype(std::declval().template get()); - -// trait checking if JSONSerializer::from_json(json const&, udt&) exists -template -struct has_from_json : std::false_type {}; - -// trait checking if j.get is valid -// use this trait instead of std::is_constructible or std::is_convertible, -// both rely on, or make use of implicit conversions, and thus fail when T -// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) -template -struct is_getable -{ - static constexpr bool value = is_detected::value; -}; - -template -struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -// This trait checks if JSONSerializer::from_json(json const&) exists -// this overload is used for non-default-constructible user-defined-types -template -struct has_non_default_from_json : std::false_type {}; - -template -struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -// This trait checks if BasicJsonType::json_serializer::to_json exists -// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. -template -struct has_to_json : std::false_type {}; - -template -struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -template -using detect_key_compare = typename T::key_compare; - -template -struct has_key_compare : std::integral_constant::value> {}; - -// obtains the actual object key comparator -template -struct actual_object_comparator -{ - using object_t = typename BasicJsonType::object_t; - using object_comparator_t = typename BasicJsonType::default_object_comparator_t; - using type = typename std::conditional < has_key_compare::value, - typename object_t::key_compare, object_comparator_t>::type; -}; - -template -using actual_object_comparator_t = typename actual_object_comparator::type; - -///////////////// -// char_traits // -///////////////// - -// Primary template of char_traits calls std char_traits -template -struct char_traits : std::char_traits -{}; - -// Explicitly define char traits for unsigned char since it is not standard -template<> -struct char_traits : std::char_traits -{ - using char_type = unsigned char; - using int_type = uint64_t; - - // Redefine to_int_type function - static int_type to_int_type(char_type c) noexcept - { - return static_cast(c); - } - - static char_type to_char_type(int_type i) noexcept - { - return static_cast(i); - } - - static constexpr int_type eof() noexcept - { - return static_cast(EOF); - } -}; - -// Explicitly define char traits for signed char since it is not standard -template<> -struct char_traits : std::char_traits -{ - using char_type = signed char; - using int_type = uint64_t; - - // Redefine to_int_type function - static int_type to_int_type(char_type c) noexcept - { - return static_cast(c); - } - - static char_type to_char_type(int_type i) noexcept - { - return static_cast(i); - } - - static constexpr int_type eof() noexcept - { - return static_cast(EOF); - } -}; - -/////////////////// -// is_ functions // -/////////////////// - -// https://en.cppreference.com/w/cpp/types/conjunction -template struct conjunction : std::true_type { }; -template struct conjunction : B { }; -template -struct conjunction -: std::conditional(B::value), conjunction, B>::type {}; - -// https://en.cppreference.com/w/cpp/types/negation -template struct negation : std::integral_constant < bool, !B::value > { }; - -// Reimplementation of is_constructible and is_default_constructible, due to them being broken for -// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). -// This causes compile errors in e.g. clang 3.5 or gcc 4.9. -template -struct is_default_constructible : std::is_default_constructible {}; - -template -struct is_default_constructible> - : conjunction, is_default_constructible> {}; - -template -struct is_default_constructible> - : conjunction, is_default_constructible> {}; - -template -struct is_default_constructible> - : conjunction...> {}; - -template -struct is_default_constructible> - : conjunction...> {}; - -template -struct is_constructible : std::is_constructible {}; - -template -struct is_constructible> : is_default_constructible> {}; - -template -struct is_constructible> : is_default_constructible> {}; - -template -struct is_constructible> : is_default_constructible> {}; - -template -struct is_constructible> : is_default_constructible> {}; - -template -struct is_iterator_traits : std::false_type {}; - -template -struct is_iterator_traits> -{ - private: - using traits = iterator_traits; - - public: - static constexpr auto value = - is_detected::value && - is_detected::value && - is_detected::value && - is_detected::value && - is_detected::value; -}; - -template -struct is_range -{ - private: - using t_ref = typename std::add_lvalue_reference::type; - - using iterator = detected_t; - using sentinel = detected_t; - - // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator - // and https://en.cppreference.com/w/cpp/iterator/sentinel_for - // but reimplementing these would be too much work, as a lot of other concepts are used underneath - static constexpr auto is_iterator_begin = - is_iterator_traits>::value; - - public: - static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; -}; - -template -using iterator_t = enable_if_t::value, result_of_begin())>>; - -template -using range_value_t = value_type_t>>; - -// The following implementation of is_complete_type is taken from -// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ -// and is written by Xiang Fan who agreed to using it in this library. - -template -struct is_complete_type : std::false_type {}; - -template -struct is_complete_type : std::true_type {}; - -template -struct is_compatible_object_type_impl : std::false_type {}; - -template -struct is_compatible_object_type_impl < - BasicJsonType, CompatibleObjectType, - enable_if_t < is_detected::value&& - is_detected::value >> -{ - using object_t = typename BasicJsonType::object_t; - - // macOS's is_constructible does not play well with nonesuch... - static constexpr bool value = - is_constructible::value && - is_constructible::value; -}; - -template -struct is_compatible_object_type - : is_compatible_object_type_impl {}; - -template -struct is_constructible_object_type_impl : std::false_type {}; - -template -struct is_constructible_object_type_impl < - BasicJsonType, ConstructibleObjectType, - enable_if_t < is_detected::value&& - is_detected::value >> -{ - using object_t = typename BasicJsonType::object_t; - - static constexpr bool value = - (is_default_constructible::value && - (std::is_move_assignable::value || - std::is_copy_assignable::value) && - (is_constructible::value && - std::is_same < - typename object_t::mapped_type, - typename ConstructibleObjectType::mapped_type >::value)) || - (has_from_json::value || - has_non_default_from_json < - BasicJsonType, - typename ConstructibleObjectType::mapped_type >::value); -}; - -template -struct is_constructible_object_type - : is_constructible_object_type_impl {}; - -template -struct is_compatible_string_type -{ - static constexpr auto value = - is_constructible::value; -}; - -template -struct is_constructible_string_type -{ - // launder type through decltype() to fix compilation failure on ICPC -#ifdef __INTEL_COMPILER - using laundered_type = decltype(std::declval()); -#else - using laundered_type = ConstructibleStringType; -#endif - - static constexpr auto value = - conjunction < - is_constructible, - is_detected_exact>::value; -}; - -template -struct is_compatible_array_type_impl : std::false_type {}; - -template -struct is_compatible_array_type_impl < - BasicJsonType, CompatibleArrayType, - enable_if_t < - is_detected::value&& - is_iterator_traits>>::value&& -// special case for types like std::filesystem::path whose iterator's value_type are themselves -// c.f. https://github.com/nlohmann/json/pull/3073 - !std::is_same>::value >> -{ - static constexpr bool value = - is_constructible>::value; -}; - -template -struct is_compatible_array_type - : is_compatible_array_type_impl {}; - -template -struct is_constructible_array_type_impl : std::false_type {}; - -template -struct is_constructible_array_type_impl < - BasicJsonType, ConstructibleArrayType, - enable_if_t::value >> - : std::true_type {}; - -template -struct is_constructible_array_type_impl < - BasicJsonType, ConstructibleArrayType, - enable_if_t < !std::is_same::value&& - !is_compatible_string_type::value&& - is_default_constructible::value&& -(std::is_move_assignable::value || - std::is_copy_assignable::value)&& -is_detected::value&& -is_iterator_traits>>::value&& -is_detected::value&& -// special case for types like std::filesystem::path whose iterator's value_type are themselves -// c.f. https://github.com/nlohmann/json/pull/3073 -!std::is_same>::value&& - is_complete_type < - detected_t>::value >> -{ - using value_type = range_value_t; - - static constexpr bool value = - std::is_same::value || - has_from_json::value || - has_non_default_from_json < - BasicJsonType, - value_type >::value; -}; - -template -struct is_constructible_array_type - : is_constructible_array_type_impl {}; - -template -struct is_compatible_integer_type_impl : std::false_type {}; - -template -struct is_compatible_integer_type_impl < - RealIntegerType, CompatibleNumberIntegerType, - enable_if_t < std::is_integral::value&& - std::is_integral::value&& - !std::is_same::value >> -{ - // is there an assert somewhere on overflows? - using RealLimits = std::numeric_limits; - using CompatibleLimits = std::numeric_limits; - - static constexpr auto value = - is_constructible::value && - CompatibleLimits::is_integer && - RealLimits::is_signed == CompatibleLimits::is_signed; -}; - -template -struct is_compatible_integer_type - : is_compatible_integer_type_impl {}; - -template -struct is_compatible_type_impl: std::false_type {}; - -template -struct is_compatible_type_impl < - BasicJsonType, CompatibleType, - enable_if_t::value >> -{ - static constexpr bool value = - has_to_json::value; -}; - -template -struct is_compatible_type - : is_compatible_type_impl {}; - -template -struct is_constructible_tuple : std::false_type {}; - -template -struct is_constructible_tuple> : conjunction...> {}; - -template -struct is_json_iterator_of : std::false_type {}; - -template -struct is_json_iterator_of : std::true_type {}; - -template -struct is_json_iterator_of : std::true_type -{}; - -// checks if a given type T is a template specialization of Primary -template