diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 12da470..89546e7 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -1,84 +1,166 @@ -name: Release +name: CD -on: +on: workflow_dispatch: + inputs: + release_tag: + description: "Git tag to publish when running manually. Leave empty to build and validate only." + required: false + type: string + c2implant_release_tag: + description: "C2Implant release tag to import. Leave empty to use latest." + required: false + type: string + c2linuximplant_release_tag: + description: "C2LinuxImplant release tag to import. Leave empty to use latest." + required: false + type: string push: tags: - - '*' - + - "*" env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_DIR: build-release BUILD_TYPE: Release + RELEASE_TARBALL: Release.tar.gz + RELEASE_ARTIFACT: C2TeamServer-Release permissions: - contents: write + contents: read + +jobs: + build-test-package: + name: Build, test, package, and validate release + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: | + C2Client/pyproject.toml + C2Client/requirements.txt + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + golang-cfssl \ + libegl1 \ + libgl1 \ + libsmbclient-dev \ + libxkbcommon-x11-0 \ + libxcb-cursor0 \ + libxcb-icccm4 \ + libxcb-image0 \ + libxcb-keysyms1 \ + libxcb-render-util0 \ + libxcb-xinerama0 + + - name: Install Conan + run: python -m pip install --upgrade pip "conan==2.24.0" + + - name: Cache Conan packages + uses: actions/cache@v4 + with: + path: ~/.conan2 + key: conan-${{ runner.os }}-${{ hashFiles('conanfile.txt', 'conan.lock', 'conan/profiles/linux-gcc13') }} + restore-keys: | + conan-${{ runner.os }}- + + - name: Configure CMake + run: > + cmake -S "${{ github.workspace }}" + -B "${{ github.workspace }}/${{ env.BUILD_DIR }}" + -DCMAKE_BUILD_TYPE="${{ env.BUILD_TYPE }}" + -DWITH_TESTS=ON + -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 "${{ env.BUILD_DIR }}" --config "${{ env.BUILD_TYPE }}" --parallel "$(nproc)" -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 + - name: Run CTest + run: ctest --test-dir "${{ env.BUILD_DIR }}" --build-config "${{ env.BUILD_TYPE }}" --output-on-failure --timeout 120 + + - name: Install client test dependencies + run: python -m pip install -e "${{ github.workspace }}/C2Client[test]" + + - name: Run client tests + env: + C2_PROTOCOL_PYTHON_ROOT: ${{ github.workspace }}/${{ env.BUILD_DIR }}/generated/python_protocol + QT_QPA_PLATFORM: offscreen + run: timeout 180 python -m pytest "${{ github.workspace }}/C2Client/tests" -vv -s + + - name: Stage TeamServer release bundle + run: cmake --build "${{ env.BUILD_DIR }}" --target validate_release_bundle --config "${{ env.BUILD_TYPE }}" + + - name: Import implant release assets + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + C2IMPLANT_RELEASE_TAG: ${{ inputs.c2implant_release_tag || '' }} + C2LINUXIMPLANT_RELEASE_TAG: ${{ inputs.c2linuximplant_release_tag || '' }} + run: | + set -euo pipefail + + args=() + if [ -n "${C2IMPLANT_RELEASE_TAG}" ]; then + args+=(--windows-tag "${C2IMPLANT_RELEASE_TAG}") + fi + if [ -n "${C2LINUXIMPLANT_RELEASE_TAG}" ]; then + args+=(--linux-tag "${C2LINUXIMPLANT_RELEASE_TAG}") + fi + + python "${{ github.workspace }}/packaging/import_implant_releases.py" \ + --stage-root "${{ github.workspace }}/${{ env.BUILD_DIR }}/release-staging/Release" \ + --import-root "${{ github.workspace }}/${{ env.BUILD_DIR }}/release-imports" \ + "${args[@]}" + + - name: Validate complete release staging + run: > + python "${{ github.workspace }}/packaging/validate_release.py" + --release-root "${{ github.workspace }}/${{ env.BUILD_DIR }}/release-staging/Release" + --require-implants + + - name: Create release archive + run: tar -C "${{ env.BUILD_DIR }}/release-staging" -czf "${{ env.RELEASE_TARBALL }}" Release + + - name: Upload validated release artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.RELEASE_ARTIFACT }} + path: ${{ env.RELEASE_TARBALL }} + if-no-files-found: error + + publish: + name: Publish GitHub release runs-on: ubuntu-22.04 + needs: build-test-package + if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && inputs.release_tag != '') + permissions: + contents: write steps: - - uses: actions/checkout@v4 - - - 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: Create default profile - run: conan profile detect - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DWITH_TESTS=OFF -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake - - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 18 - - - name: Prep release - 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 - - - name: Upload release - uses: svenstaro/upload-release-action@v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: Release.tar.gz - asset_name: Release.tar.gz - tag: ${{ github.ref }} - overwrite: true - body: "C2TeamServer client and server, include the release of C2Implant with the windows beacons and modules" + - name: Download validated release artifact + uses: actions/download-artifact@v4 + with: + name: ${{ env.RELEASE_ARTIFACT }} + + - name: Upload release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: ${{ env.RELEASE_TARBALL }} + asset_name: ${{ env.RELEASE_TARBALL }} + tag: ${{ inputs.release_tag || github.ref }} + overwrite: true + body: "C2TeamServer client, server, TeamServer modules, and validated C2Implant/C2LinuxImplant beacons and modules." diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml index 942d731..19fab5d 100644 --- a/.github/workflows/Tests.yml +++ b/.github/workflows/Tests.yml @@ -1,55 +1,98 @@ -name: Tests +name: CI -on: workflow_dispatch +on: + workflow_dispatch: + pull_request: + push: + branches: + - "**" env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_DIR: build-ci BUILD_TYPE: Release -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 +permissions: + contents: read + +jobs: + build-test: + name: Build, test, and validate staging runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - - - 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: Create default profile - run: conan profile detect - - - name: Configure CMake for tests - run: cmake -B ${{github.workspace}}/build -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake - - - name: Build for test - run: cmake --build ${{github.workspace}}/build -j 18 - - - name: Run unit tests - run: ctest --test-dir build - - - 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 - - - name: Build like the release - run: cmake --build ${{github.workspace}}/buildRelease -j 18 + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: | + C2Client/pyproject.toml + C2Client/requirements.txt + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + golang-cfssl \ + libegl1 \ + libgl1 \ + libsmbclient-dev \ + libxkbcommon-x11-0 \ + libxcb-cursor0 \ + libxcb-icccm4 \ + libxcb-image0 \ + libxcb-keysyms1 \ + libxcb-render-util0 \ + libxcb-xinerama0 + + - name: Install Conan + run: python -m pip install --upgrade pip "conan==2.24.0" + + - name: Cache Conan packages + uses: actions/cache@v4 + with: + path: ~/.conan2 + key: conan-${{ runner.os }}-${{ hashFiles('conanfile.txt', 'conan.lock', 'conan/profiles/linux-gcc13') }} + restore-keys: | + conan-${{ runner.os }}- + + - name: Configure CMake + run: > + cmake -S "${{ github.workspace }}" + -B "${{ github.workspace }}/${{ env.BUILD_DIR }}" + -DCMAKE_BUILD_TYPE="${{ env.BUILD_TYPE }}" + -DWITH_TESTS=ON + -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 "${{ env.BUILD_DIR }}" --config "${{ env.BUILD_TYPE }}" --parallel "$(nproc)" + + - name: Run CTest + run: ctest --test-dir "${{ env.BUILD_DIR }}" --build-config "${{ env.BUILD_TYPE }}" --output-on-failure --timeout 120 + + - name: Install client test dependencies + run: python -m pip install -e "${{ github.workspace }}/C2Client[test]" + + - name: Run client tests + env: + C2_PROTOCOL_PYTHON_ROOT: ${{ github.workspace }}/${{ env.BUILD_DIR }}/generated/python_protocol + QT_QPA_PLATFORM: offscreen + run: timeout 180 python -m pytest "${{ github.workspace }}/C2Client/tests" -vv -s + + - name: Validate release staging + run: | + cmake --build "${{ env.BUILD_DIR }}" --target validate_release_bundle --config "${{ env.BUILD_TYPE }}" + + - name: Validate integration runtime staging + run: | + cmake --build "${{ env.BUILD_DIR }}" --target stage_integration_runtime --config "${{ env.BUILD_TYPE }}" + test -x "${{ github.workspace }}/${{ env.BUILD_DIR }}/integration-staging/runtime/Release/TeamServer/TeamServer" + test -f "${{ github.workspace }}/${{ env.BUILD_DIR }}/integration-staging/runtime/Release/Client/c2client_protocol/TeamServerApi_pb2.py" diff --git a/.gitignore b/.gitignore index e6b6685..c61eb1d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,12 @@ Tests C2Client/build/ .vscode build/ +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/AGENT.md b/AGENT.md index 7a90556..e823246 100644 --- a/AGENT.md +++ b/AGENT.md @@ -1,58 +1,162 @@ -# 🧠 AGENT.md +# AGENT.md ## Agent Role -You are an **expert C++ and CMake assistant** dedicated to supporting 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**. - ---- - -## 📌 Responsibilities - -### Code Review & Navigation - -* 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. - -### Style & Syntax Alignment - -* 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. - -### Documentation & Guidance - -* 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. - -### Troubleshooting and Q\&A - -* 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. - ---- - -## 🚫 What You Should Not Do - -* 🛠 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. - ---- - -## ✅ Summary - -| 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 | - ---- - -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. +You are an expert C++ and CMake assistant working on the C2TeamServer codebase. + +- 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 + +From now on, the project must be compiled and tested during implementation work when the change can affect build or runtime behavior. + +- 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. +- 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, validate `validate_release_bundle`. For complete release checks, import implant assets into staging and run `packaging/validate_release.py --require-implants`. +- For future integration work, prefer validating `stage_integration_runtime` too. + +## Validated Environment + +The following toolchain was verified successfully in WSL: + +- `cmake 3.28.3` +- `conan 2.24.0` +- `gcc 13.3.0` +- `make 4.3` + +## Validated Build Commands + +Run the project from scratch with: + +```bash +cd /home/max/project/C2TeamServer +mkdir -p build-codex-scratch +cd build-codex-scratch +cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DWITH_TESTS=ON \ + -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 --timeout 120 +``` + +Run the Python client tests with: + +```bash +sudo apt-get install -y \ + libegl1 \ + libgl1 \ + libxkbcommon-x11-0 \ + libxcb-cursor0 \ + libxcb-icccm4 \ + libxcb-image0 \ + libxcb-keysyms1 \ + libxcb-render-util0 \ + libxcb-xinerama0 + +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 -vv -s +``` + +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. +- Prefer the checked-in Conan profile and lockfile to keep the graph stable across local builds and CI. +- The validated root build currently executes `63` CTest tests and `5` Python client tests. +- `libs/libDns/tests/fonctionalTest` is compiled but intentionally excluded from CTest because it is a manual server/client harness that needs explicit arguments. +- The staged release bundle is produced and validated with `cmake --build --target validate_release_bundle` under `/release-staging/Release`. +- The staged integration runtime is produced with `cmake --build --target stage_integration_runtime` under `/integration-staging/runtime/Release`. + +## Release Contract + +CI and CD must keep a clean staging-first release flow. + +- CI runs on pull requests and branch pushes with read-only GitHub permissions. +- CD runs on tags or manual dispatch. Publishing is the only job with `contents: write`. +- Releases are built with tests enabled, then CTest and client pytest run before any archive is created. +- The final `Release.tar.gz` must be created only from `/release-staging/Release`. +- Do not rename, delete, or mutate source directories while packaging. + +The TeamServer release staging layout is: + +```text +Release/ + TeamServer/ + TeamServerModules/ + Client/ + WindowsBeacons/ + WindowsModules/ + LinuxBeacons/ + LinuxModules/ +``` + +Imported implant archives must use the refactored layouts: + +```text +C2Implant: Release/WindowsBeacons and Release/WindowsModules +C2LinuxImplant: Release/LinuxBeacons and Release/LinuxModules +``` + +The import script defaults to the latest GitHub release for each implant repo. +When a release must be reproducible, pass explicit tags: + +```bash +python packaging/import_implant_releases.py \ + --stage-root build-codex-scratch/release-staging/Release \ + --import-root build-codex-scratch/release-imports \ + --windows-tag vX.Y.Z \ + --linux-tag vX.Y.Z +python packaging/validate_release.py \ + --release-root build-codex-scratch/release-staging/Release \ + --require-implants +``` + +## 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. + +## Working Rules + +- 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. + +## Core Platform Duality + +Shared code under `core/` is not symmetric across platforms. + +- On Windows beacon-side code, HTTP/HTTPS and GitHub transports may use `WinHTTP`, `WinCrypt`, `BCrypt`, and other WinAPI facilities. +- On Linux, the equivalent transport code may use `httplib` and `OpenSSL`. +- Do not add Linux transport dependencies to Windows targets just because the source file lives under `core/`. +- Keep transport-specific link dependencies conditional on platform when editing `core` CMake files. +- Apply the same rule to tests: do not make Windows test targets depend on Linux-only imported targets unless the Windows code path actually uses them. + +## Summary + +- Primary role: senior C++/CMake copilot for C2TeamServer. +- Required workflow: inspect, edit, compile, test, then report. +- Verified build path: WSL + Conan + CMake + Make + GCC. diff --git a/C2Client/C2Client/grpcClient.py b/C2Client/C2Client/grpcClient.py index c937f23..fbc9682 100644 --- a/C2Client/C2Client/grpcClient.py +++ b/C2Client/C2Client/grpcClient.py @@ -7,15 +7,11 @@ import logging import os -import sys 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 .protocol_bindings import TeamServerApi_pb2, TeamServerApi_pb2_grpc MetadataType = List[Tuple[str, str]] @@ -216,4 +212,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/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..8b94268 --- /dev/null +++ b/C2Client/C2Client/protocol_bindings.py @@ -0,0 +1,64 @@ +"""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]: + 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") + 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..5e2be27 100644 --- a/C2Client/pyproject.toml +++ b/C2Client/pyproject.toml @@ -8,10 +8,10 @@ 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.32.0", + "protobuf==6.33.5", "gitpython==3.1.45", "requests==2.32.5", "pwn==1.0", @@ -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/requirements.txt b/C2Client/requirements.txt index 9829abf..3ba9826 100644 --- a/C2Client/requirements.txt +++ b/C2Client/requirements.txt @@ -1,8 +1,8 @@ pycryptodome==3.23.0 -grpcio==1.74.0 +grpcio==1.78.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/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/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 7c56814..60858f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,71 +1,144 @@ 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) +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}" +) ## ## Conan Dependencies ## -set(CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR}) - -find_package(gRPC REQUIRED) -find_package(OpenSSL REQUIRED) -find_package(protobuf REQUIRED) -find_package(ZLIB REQUIRED) -find_package(spdlog REQUIRED) -find_package(httplib REQUIRED) - -include_directories(${CMAKE_INCLUDE_PATH}) - - -## -## 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() +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(Libssh2 REQUIRED) +find_package(OpenSSL REQUIRED) +find_package(protobuf REQUIRED) +find_package(ZLIB REQUIRED) +find_package(spdlog REQUIRED) +find_package(httplib 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}) + + +## +## Config Tests et Logs +## + +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) + enable_testing() +endif() ## ## Build ## -include_directories(thirdParty) - -add_subdirectory(libs) - -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" -) - -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) - -add_subdirectory(certs) - +add_subdirectory("${C2_LIBS_DIR}") + +if(WITH_TESTS) + function(c2_register_existing_test target_name) + if(TARGET "${target_name}") + add_test(NAME "${target_name}" COMMAND "$") + endif() + endfunction() + + foreach(C2_LIB_TEST + TestsSockerHandler + dnsTest + utilsTest + responseDecodeTest + messageTest + interleavedTest + TestsMemoryModule + TestsSocksServer) + c2_register_existing_test("${C2_LIB_TEST}") + endforeach() + # libs/libDns/tests/fonctionalTest is a manual server/client harness and + # needs explicit runtime arguments, so it is intentionally not a CI test. +endif() + +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" +) + +include_directories("${C2_CORE_DIR}/listener") +include_directories("${C2_CORE_DIR}/beacon") +include_directories("${C2_CORE_DIR}/modules/ModuleCmd") + +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/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..88bc1d4 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** @@ -62,6 +80,7 @@ To launch the TeamServer: ```bash cd Release +cd TeamServer ./TeamServer ``` @@ -69,42 +88,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/): - -```bash -# uv -uv tool install git+https://github.com/maxDcb/C2TeamServer.git#subdirectory=C2Client -``` +Install the Python client from the staged release bundle or from the repository sources. -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 +146,199 @@ 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 + +Validated commands in WSL/Linux: + +```bash +git clone https://github.com/maxDcb/C2TeamServer.git +cd C2TeamServer +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 \ + -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 +``` + +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 `63` CTest tests from the root build, including one staged-runtime integration test. +- `libs/libDns/tests/fonctionalTest` is built but not registered in CTest because it is a manual server/client harness that requires explicit runtime arguments. + +### 🧪 Client Tests + +The client depends on the generated Python protocol package produced by the root CMake build: + +```bash +sudo apt-get install -y \ + libegl1 \ + libgl1 \ + libxkbcommon-x11-0 \ + libxcb-cursor0 \ + libxcb-icccm4 \ + libxcb-image0 \ + libxcb-keysyms1 \ + libxcb-render-util0 \ + libxcb-xinerama0 + +cd C2Client +python -m venv .venv +. .venv/bin/activate +pip install -e .[test] + +export C2_PROTOCOL_PYTHON_ROOT=$PWD/../build/generated/python_protocol +pytest tests -vv -s +``` + +### 📦 Stage A Release Bundle + +To assemble the local release bundle from build outputs: + +```bash +cmake --build build --target validate_release_bundle +``` + +The bundle is created under: -### 🔧 Build Process +```text +build/release-staging/Release +``` + +This staged bundle contains: + +- `TeamServer` +- `TeamServerModules` +- the generated Python client protocol package +- the Python client sources and launchers + +The base TeamServer bundle is validated before it can be published. The +validation checks the TeamServer binary, runtime config, certificates, generated +client protocol package, and the complete `TeamServerModules` layout. + +### Implant Release Layout + +The TeamServer release also embeds validated release assets from: + +- [C2Implant](https://github.com/maxDcb/C2Implant) +- [C2LinuxImplant](https://github.com/maxDcb/C2LinuxImplant) + +The expected staged TeamServer release layout is: + +```text +Release/ + TeamServer/ + TeamServerModules/ + Client/ + WindowsBeacons/ + WindowsModules/ + LinuxBeacons/ + LinuxModules/ +``` + +The imported Windows implant archive must expose: + +```text +Release/WindowsBeacons/ +Release/WindowsModules/ +``` + +The imported Linux implant archive must expose: + +```text +Release/LinuxBeacons/ +Release/LinuxModules/ +``` -1. **Install Dependencies**: +Legacy `Release/Beacons` and `Release/Modules` layouts are intentionally not +accepted by the TeamServer CD pipeline. If an implant repository changes its +release contract, update the validation scripts and this section in the same +change. - * Ensure you have **CMake**, **g++**, and **Conan** installed. +To import and validate implant assets locally: - ```bash - sudo apt install cmake - pip3 install conan - ``` +```bash +python packaging/import_implant_releases.py \ + --stage-root build/release-staging/Release \ + --import-root build/release-imports + +python packaging/validate_release.py \ + --release-root build/release-staging/Release \ + --require-implants +``` + +By default the import script uses the latest GitHub release from each implant +repository. To avoid that moving target, pass explicit release tags: + +```bash +python packaging/import_implant_releases.py \ + --stage-root build/release-staging/Release \ + --import-root build/release-imports \ + --windows-tag vX.Y.Z \ + --linux-tag vX.Y.Z +``` + +Create the final archive only from the validated staging directory: -2. **Clone the Repository**: - If you haven't cloned the repository already, you can do so with: +```bash +tar -C build/release-staging -czf Release.tar.gz Release +``` + +### Prepare The Integration Runtime + +The root build provides a dedicated preparation target for integration tests: - ```bash - git clone https://github.com/maxDcb/C2TeamServer.git - cd C2TeamServer - git submodule update --init - ``` +```bash +cmake --build build --target stage_integration_runtime +``` -3. **Configure the Build**: +This produces a deterministic runtime under: + +```text +build/integration-staging/runtime/Release +``` - * Create a build directory and navigate into it. +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. - ```bash - mkdir build - cd build - ``` +## CI/CD Contract - * 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. +The repository uses two GitHub Actions workflows: - ```bash - cmake .. -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./conan_provider.cmake - ``` +- `CI` (`.github/workflows/Tests.yml`) runs on pull requests and branch pushes. +- `CD` (`.github/workflows/Release.yml`) runs on tags and manual dispatch. -4. **Build the TeamServer**: +The CI workflow: - * Now, you can compile the TeamServer with the following command: +- installs system dependencies, Python, Conan, and client test dependencies + explicitly, including Qt runtime libraries required by `pytest-qt`; +- uses Conan and pip caches keyed from checked-in dependency files; +- configures a Release CMake build with tests enabled; +- builds the TeamServer and modules; +- runs CTest with failure output and a timeout; +- runs the Python client pytest suite with verbose output and a timeout; +- validates the staged TeamServer release bundle. - ```bash - make - ``` +The CD workflow repeats the build and test gates, imports implant release +assets into staging, validates the complete release layout, creates +`Release.tar.gz` from staging, and publishes only that validated archive. - * 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. +GitHub Actions permissions are read-only by default. `contents: write` is +granted only to the publish job that uploads the GitHub release asset. 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 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/conan.lock b/conan.lock new file mode 100644 index 0000000..f57688d --- /dev/null +++ b/conan.lock @@ -0,0 +1,27 @@ +{ + "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", + "nlohmann_json/3.12.0#2d634ab0ec8d9f56353e5ccef6d6612c%1744735883.94", + "libssh2/1.11.1#f96f381452c195e48da48b78ad4fc82f%1743424231.27", + "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/conanfile.txt b/conanfile.txt index 4e2fbaa..b6549cb 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,9 +1,14 @@ [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 +nlohmann_json/3.12.0 +libssh2/1.11.1 + +[options] +libssh2/*:shared=False [layout] cmake_layout diff --git a/core b/core index 2f6652d..9ebd61c 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 2f6652ddbb70378c187e06d64285175f2f5cfcfb +Subproject commit 9ebd61c8b22e191e2589cf547e6ad26e82dfb6ff 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/libs/CMakeLists.txt b/libs/CMakeLists.txt index ad9028f..b21f5e0 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -1,44 +1,7 @@ -# 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=${CMAKE_SOURCE_DIR}/C2Client/C2Client/libGrpcMessages/build/py - --python_out=${CMAKE_SOURCE_DIR}/C2Client/C2Client/libGrpcMessages/build/py - --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) add_subdirectory(libMemoryModuleDumy) add_subdirectory(libPipeHandlerDumy) -add_subdirectory(libSocks5) \ No newline at end of file +add_subdirectory(libSocks5) 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/libs/libSocks5 b/libs/libSocks5 index 1c6777a..8ef9dd3 160000 --- a/libs/libSocks5 +++ b/libs/libSocks5 @@ -1 +1 @@ -Subproject commit 1c6777a2388e517c6fc2193a7b2f6897ade8e7cb +Subproject commit 8ef9dd3549ca1a06000628ea6be6dcc3935a9969 diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt new file mode 100644 index 0000000..bb4f444 --- /dev/null +++ b/packaging/CMakeLists.txt @@ -0,0 +1,29 @@ +find_package(Python3 COMPONENTS Interpreter REQUIRED) + +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} + "${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 +) + +add_custom_target(validate_release_bundle + COMMAND ${Python3_EXECUTABLE} + "${CMAKE_CURRENT_SOURCE_DIR}/validate_release.py" + --release-root "${C2_RELEASE_STAGING_DIR}" + DEPENDS stage_release_bundle + COMMENT "Validating staged release bundle" + VERBATIM +) diff --git a/packaging/assemble_release.py b/packaging/assemble_release.py new file mode 100644 index 0000000..7dc9fe9 --- /dev/null +++ b/packaging/assemble_release.py @@ -0,0 +1,142 @@ +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: + build_release_root = build_root / "artifacts" / "Release" + teamserver_root = build_release_root / "TeamServer" + modules_root = build_release_root / "TeamServerModules" + + 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) + output_root.mkdir(parents=True, exist_ok=True) + + 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") + 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) + + _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() diff --git a/packaging/import_implant_releases.py b/packaging/import_implant_releases.py new file mode 100644 index 0000000..3407db4 --- /dev/null +++ b/packaging/import_implant_releases.py @@ -0,0 +1,217 @@ +from __future__ import annotations + +import argparse +import json +import os +import shutil +import sys +import tarfile +import urllib.error +import urllib.request +import zipfile +from pathlib import Path +from typing import Iterable + +from validate_release import ( + EXPECTED_LINUX_BEACONS, + EXPECTED_LINUX_MODULES, + EXPECTED_WINDOWS_BEACONS, + EXPECTED_WINDOWS_MODULES, + ValidationError, + _require_directory_exact, +) + + +DEFAULT_WINDOWS_REPO = "maxDcb/C2Implant" +DEFAULT_LINUX_REPO = "maxDcb/C2LinuxImplant" + + +def _request(url: str, token: str | None = None) -> urllib.request.Request: + headers = { + "Accept": "application/vnd.github+json", + "User-Agent": "C2TeamServer-release-packaging", + } + if token: + headers["Authorization"] = f"Bearer {token}" + return urllib.request.Request(url, headers=headers) + + +def _read_json(url: str, token: str | None) -> dict: + try: + with urllib.request.urlopen(_request(url, token), timeout=60) as response: + return json.loads(response.read().decode("utf-8")) + except urllib.error.HTTPError as exc: + detail = exc.read().decode("utf-8", errors="replace") + raise RuntimeError(f"GitHub API request failed for {url}: HTTP {exc.code}: {detail}") from exc + + +def _release_api_url(repo: str, tag: str | None) -> str: + if tag: + return f"https://api.github.com/repos/{repo}/releases/tags/{tag}" + return f"https://api.github.com/repos/{repo}/releases/latest" + + +def _find_asset_url(release_data: dict, asset_name: str, repo: str) -> str: + for asset in release_data.get("assets", []): + if asset.get("name") == asset_name: + url = asset.get("browser_download_url") + if not url: + raise RuntimeError(f"Asset {asset_name} in {repo} has no browser_download_url") + return url + available = ", ".join(asset.get("name", "") for asset in release_data.get("assets", [])) + raise RuntimeError(f"Release for {repo} does not contain {asset_name}. Available assets: {available}") + + +def _download(url: str, destination: Path, token: str | None) -> None: + destination.parent.mkdir(parents=True, exist_ok=True) + try: + with urllib.request.urlopen(_request(url, token), timeout=120) as response: + with destination.open("wb") as output: + shutil.copyfileobj(response, output) + except urllib.error.HTTPError as exc: + detail = exc.read().decode("utf-8", errors="replace") + raise RuntimeError(f"Download failed for {url}: HTTP {exc.code}: {detail}") from exc + + if destination.stat().st_size == 0: + raise RuntimeError(f"Downloaded asset is empty: {destination}") + + +def _assert_safe_archive_member(base_dir: Path, member_name: str) -> None: + member_path = (base_dir / member_name).resolve() + if not str(member_path).startswith(str(base_dir.resolve()) + os.sep): + raise RuntimeError(f"Archive contains unsafe path: {member_name}") + + +def _extract_zip(archive_path: Path, destination: Path) -> None: + destination.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(archive_path) as archive: + for member in archive.namelist(): + _assert_safe_archive_member(destination, member) + archive.extractall(destination) + + +def _extract_tar(archive_path: Path, destination: Path) -> None: + destination.mkdir(parents=True, exist_ok=True) + with tarfile.open(archive_path, "r:gz") as archive: + for member in archive.getmembers(): + _assert_safe_archive_member(destination, member.name) + archive.extractall(destination) + + +def _copy_validated_dir(source: Path, destination: Path, expected_files: tuple[str, ...]) -> None: + _require_directory_exact(source, expected_files) + shutil.rmtree(destination, ignore_errors=True) + shutil.copytree(source, destination) + _require_directory_exact(destination, expected_files) + + +def _fetch_release_asset( + repo: str, + tag: str | None, + asset_name: str, + destination: Path, + token: str | None, +) -> None: + release_data = _read_json(_release_api_url(repo, tag), token) + tag_label = release_data.get("tag_name", tag or "latest") + asset_url = _find_asset_url(release_data, asset_name, repo) + print(f"Downloading {repo} {tag_label} asset {asset_name}") + _download(asset_url, destination, token) + + +StageCopy = tuple[Path, Path, tuple[str, ...]] + + +def _prepare_windows(repo: str, tag: str | None, import_root: Path, stage_root: Path, token: str | None) -> list[StageCopy]: + archive_path = import_root / "C2Implant.zip" + extract_root = import_root / "windows" + _fetch_release_asset(repo, tag, "Release.zip", archive_path, token) + _extract_zip(archive_path, extract_root) + + release_root = extract_root / "Release" + windows_beacons = release_root / "WindowsBeacons" + windows_modules = release_root / "WindowsModules" + _require_directory_exact(windows_beacons, EXPECTED_WINDOWS_BEACONS) + _require_directory_exact(windows_modules, EXPECTED_WINDOWS_MODULES) + return [ + (windows_beacons, stage_root / "WindowsBeacons", EXPECTED_WINDOWS_BEACONS), + (windows_modules, stage_root / "WindowsModules", EXPECTED_WINDOWS_MODULES), + ] + + +def _prepare_linux(repo: str, tag: str | None, import_root: Path, stage_root: Path, token: str | None) -> list[StageCopy]: + archive_path = import_root / "C2LinuxImplant.tar.gz" + extract_root = import_root / "linux" + _fetch_release_asset(repo, tag, "Release.tar.gz", archive_path, token) + _extract_tar(archive_path, extract_root) + + release_root = extract_root / "Release" + linux_beacons = release_root / "LinuxBeacons" + linux_modules = release_root / "LinuxModules" + _require_directory_exact(linux_beacons, EXPECTED_LINUX_BEACONS) + _require_directory_exact(linux_modules, EXPECTED_LINUX_MODULES) + return [ + (linux_beacons, stage_root / "LinuxBeacons", EXPECTED_LINUX_BEACONS), + (linux_modules, stage_root / "LinuxModules", EXPECTED_LINUX_MODULES), + ] + + +def _none_if_blank(value: str | None) -> str | None: + return value if value else None + + +def main(argv: Iterable[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description="Import C2Implant and C2LinuxImplant release assets into TeamServer staging." + ) + parser.add_argument("--stage-root", required=True, help="Path to the staged TeamServer Release directory.") + parser.add_argument("--import-root", required=True, help="Scratch directory used for downloads and extraction.") + parser.add_argument("--windows-repo", default=DEFAULT_WINDOWS_REPO) + parser.add_argument("--linux-repo", default=DEFAULT_LINUX_REPO) + parser.add_argument("--windows-tag", default="") + parser.add_argument("--linux-tag", default="") + args = parser.parse_args(argv) + + stage_root = Path(args.stage_root).resolve() + import_root = Path(args.import_root).resolve() + token = os.environ.get("GITHUB_TOKEN") + + try: + if not stage_root.is_dir(): + raise ValidationError(f"Stage root does not exist: {stage_root}") + + shutil.rmtree(import_root, ignore_errors=True) + import_root.mkdir(parents=True, exist_ok=True) + + copy_plan: list[StageCopy] = [] + copy_plan.extend( + _prepare_windows( + args.windows_repo, + _none_if_blank(args.windows_tag), + import_root, + stage_root, + token, + ) + ) + copy_plan.extend( + _prepare_linux( + args.linux_repo, + _none_if_blank(args.linux_tag), + import_root, + stage_root, + token, + ) + ) + + for source, destination, expected_files in copy_plan: + _copy_validated_dir(source, destination, expected_files) + except (RuntimeError, ValidationError, zipfile.BadZipFile, tarfile.TarError) as exc: + print(f"Implant import failed: {exc}", file=sys.stderr) + return 1 + + print(f"Imported implant release assets into {stage_root}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/packaging/validate_release.py b/packaging/validate_release.py new file mode 100644 index 0000000..a584989 --- /dev/null +++ b/packaging/validate_release.py @@ -0,0 +1,256 @@ +from __future__ import annotations + +import argparse +import os +import sys +from pathlib import Path + + +EXPECTED_TEAMSERVER_FILES = ( + "TeamServer", + "TeamServerConfig.json", + "auth_credentials.json", + "localhost.crt", + "localhost.key", + "rootCA.crt", + "server.crt", + "server.key", +) + +EXPECTED_TEAMSERVER_MODULES = ( + "libAssemblyExec.so", + "libCat.so", + "libChangeDirectory.so", + "libChisel.so", + "libCimExec.so", + "libCoff.so", + "libDcomExec.so", + "libDotnetExec.so", + "libDownload.so", + "libEnumerateRdpSessions.so", + "libEnumerateShares.so", + "libEvasion.so", + "libGetEnv.so", + "libInject.so", + "libIpConfig.so", + "libKerberosUseTicket.so", + "libKeyLogger.so", + "libKillProcess.so", + "libListDirectory.so", + "libListProcesses.so", + "libMakeToken.so", + "libMiniDump.so", + "libMkDir.so", + "libNetstat.so", + "libPowershell.so", + "libPrintWorkingDirectory.so", + "libPsExec.so", + "libPwSh.so", + "libRegistry.so", + "libRemove.so", + "libRev2self.so", + "libRun.so", + "libScreenShot.so", + "libScript.so", + "libShell.so", + "libSpawnAs.so", + "libSshExec.so", + "libStealToken.so", + "libTaskScheduler.so", + "libTree.so", + "libUpload.so", + "libWhoami.so", + "libWinRM.so", + "libWmiExec.so", +) + +EXPECTED_WINDOWS_BEACONS = ( + "BeaconDns.exe", + "BeaconDnsDll.dll", + "BeaconGithub.exe", + "BeaconGithubDll.dll", + "BeaconHttp.exe", + "BeaconHttpDll.dll", + "BeaconSmb.exe", + "BeaconSmbDll.dll", + "BeaconTcp.exe", + "BeaconTcpDll.dll", +) + +EXPECTED_WINDOWS_MODULES = ( + "AssemblyExec.dll", + "Cat.dll", + "ChangeDirectory.dll", + "Chisel.dll", + "CimExec.dll", + "Coff.dll", + "DcomExec.dll", + "DotnetExec.dll", + "Download.dll", + "EnumerateRdpSessions.dll", + "EnumerateShares.dll", + "Evasion.dll", + "GetEnv.dll", + "Inject.dll", + "IpConfig.dll", + "KerberosUseTicket.dll", + "KeyLogger.dll", + "KillProcess.dll", + "ListDirectory.dll", + "ListProcesses.dll", + "MakeToken.dll", + "MiniDump.dll", + "MkDir.dll", + "Netstat.dll", + "Powershell.dll", + "PrintWorkingDirectory.dll", + "PsExec.dll", + "PwSh.dll", + "Registry.dll", + "Remove.dll", + "Rev2self.dll", + "Run.dll", + "ScreenShot.dll", + "Script.dll", + "Shell.dll", + "SpawnAs.dll", + "SshExec.dll", + "StealToken.dll", + "TaskScheduler.dll", + "Tree.dll", + "Upload.dll", + "Whoami.dll", + "WinRM.dll", + "WmiExec.dll", +) + +EXPECTED_LINUX_BEACONS = ( + "BeaconDns", + "BeaconGithub", + "BeaconHttp", + "BeaconSmb", + "BeaconTcp", +) + +EXPECTED_LINUX_MODULES = EXPECTED_TEAMSERVER_MODULES + + +class ValidationError(RuntimeError): + pass + + +def _relative_files(root: Path) -> set[str]: + return { + path.relative_to(root).as_posix() + for path in root.rglob("*") + if path.is_file() + } + + +def _require_non_empty_file(path: Path) -> None: + if not path.is_file(): + raise ValidationError(f"Missing required file: {path}") + if path.stat().st_size == 0: + raise ValidationError(f"Required file is empty: {path}") + + +def _require_executable(path: Path) -> None: + _require_non_empty_file(path) + if os.name != "nt" and not os.access(path, os.X_OK): + raise ValidationError(f"Required file is not executable: {path}") + + +def _require_directory_exact(root: Path, expected_files: tuple[str, ...]) -> None: + if not root.is_dir(): + raise ValidationError(f"Missing required directory: {root}") + + expected = set(expected_files) + actual = _relative_files(root) + missing = sorted(expected - actual) + unexpected = sorted(actual - expected) + + if missing: + raise ValidationError(f"{root} is missing expected file(s): {', '.join(missing)}") + if unexpected: + raise ValidationError(f"{root} contains unexpected file(s): {', '.join(unexpected)}") + + for relative in expected_files: + _require_non_empty_file(root / relative) + + +def validate_base_release(release_root: Path) -> None: + if not release_root.is_dir(): + raise ValidationError(f"Release root does not exist: {release_root}") + + teamserver_root = release_root / "TeamServer" + modules_root = release_root / "TeamServerModules" + client_root = release_root / "Client" + + if not teamserver_root.is_dir(): + raise ValidationError(f"Missing TeamServer directory: {teamserver_root}") + for filename in EXPECTED_TEAMSERVER_FILES: + path = teamserver_root / filename + if filename == "TeamServer": + _require_executable(path) + else: + _require_non_empty_file(path) + + if not (teamserver_root / "logs").is_dir(): + raise ValidationError(f"Missing TeamServer logs directory: {teamserver_root / 'logs'}") + + _require_directory_exact(modules_root, EXPECTED_TEAMSERVER_MODULES) + + _require_non_empty_file(client_root / "README.md") + _require_non_empty_file(client_root / "pyproject.toml") + _require_non_empty_file(client_root / "requirements.txt") + _require_executable(client_root / "run-client.sh") + _require_non_empty_file(client_root / "run-client.ps1") + if not (client_root / "c2client_protocol" / "__init__.py").is_file(): + raise ValidationError( + f"Missing required file: {client_root / 'c2client_protocol' / '__init__.py'}" + ) + _require_non_empty_file(client_root / "c2client_protocol" / "TeamServerApi_pb2.py") + _require_non_empty_file(client_root / "c2client_protocol" / "TeamServerApi_pb2_grpc.py") + + generated_noise = [ + path for path in release_root.rglob("*") if path.name in {".gitignore", "__pycache__"} + ] + if generated_noise: + raise ValidationError( + "Release staging contains generated/source-control noise: " + + ", ".join(str(path) for path in generated_noise) + ) + + +def validate_implants(release_root: Path) -> None: + _require_directory_exact(release_root / "WindowsBeacons", EXPECTED_WINDOWS_BEACONS) + _require_directory_exact(release_root / "WindowsModules", EXPECTED_WINDOWS_MODULES) + _require_directory_exact(release_root / "LinuxBeacons", EXPECTED_LINUX_BEACONS) + _require_directory_exact(release_root / "LinuxModules", EXPECTED_LINUX_MODULES) + + +def main() -> int: + parser = argparse.ArgumentParser(description="Validate a staged C2TeamServer release.") + parser.add_argument("--release-root", required=True, help="Path to the staged Release directory.") + parser.add_argument( + "--require-implants", + action="store_true", + help="Require and validate imported C2Implant/C2LinuxImplant assets.", + ) + args = parser.parse_args() + + release_root = Path(args.release_root).resolve() + try: + validate_base_release(release_root) + if args.require_implants: + validate_implants(release_root) + except ValidationError as exc: + print(f"Release validation failed: {exc}", file=sys.stderr) + return 1 + + print(f"Validated release staging: {release_root}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/protocol/CMakeLists.txt b/protocol/CMakeLists.txt new file mode 100644 index 0000000..9788058 --- /dev/null +++ b/protocol/CMakeLists.txt @@ -0,0 +1,67 @@ +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" + "${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" +) +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} + 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} ${Protobuf_PROTOC_EXECUTABLE} ${GRPC_CPP_PLUGIN_PROGRAM} + 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} ${Protobuf_PROTOC_EXECUTABLE} ${GRPC_PYTHON_PLUGIN_PROGRAM} + VERBATIM +) + +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) +target_include_directories(GrpcMessages PUBLIC "${C2_PROTOCOL_CPP_OUT_DIR}") +target_link_libraries(GrpcMessages PUBLIC protobuf::libprotobuf grpc::grpc) 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" "") 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; diff --git a/teamServer/CMakeLists.txt b/teamServer/CMakeLists.txt index 01b9b79..08910b0 100644 --- a/teamServer/CMakeLists.txt +++ b/teamServer/CMakeLists.txt @@ -1,38 +1,134 @@ - include_directories(../core) include_directories(../core/modules/ModuleCmd) -set(SOURCES_TEAMSERVER -teamServer/TeamServer.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 -) - -## TeamServer -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) -endif() +set(TEAMSERVER_CORE_SOURCES + 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 + teamServer/TeamServerBootstrap.cpp + teamServer/TeamServerListenerSessionService.cpp +) + +set(SERVER_LISTENER_TRANSPORT_SOURCES + ../core/listener/Listener.cpp + ../core/listener/ListenerTcp.cpp + ../core/listener/ListenerHttp.cpp + ../core/listener/ListenerGithub.cpp + ../core/listener/ListenerDns.cpp +) + +set(SERVER_LISTENER_TRANSPORT_LINK_LIBS + Dnscommunication + SocketHandler + openssl::openssl + spdlog::spdlog + httplib::httplib + pthread + dl + rt +) + +set(TEAMSERVER_CORE_LINK_LIBS + GrpcMessages + pthread + openssl::openssl + ZLIB::ZLIB + grpc::grpc + spdlog::spdlog + SocksServer + dl + rt +) -add_custom_command(TARGET TeamServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy -$ "${CMAKE_SOURCE_DIR}/Release/TeamServer/$") -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") -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") +add_library(server_listener_transport STATIC ${SERVER_LISTENER_TRANSPORT_SOURCES}) +target_include_directories(server_listener_transport + PUBLIC + ${CMAKE_SOURCE_DIR}/core + ${CMAKE_SOURCE_DIR}/core/listener + ${CMAKE_SOURCE_DIR}/core/modules/ModuleCmd +) +target_link_libraries(server_listener_transport + PUBLIC + ${SERVER_LISTENER_TRANSPORT_LINK_LIBS} + PRIVATE + c2_base64 +) + +add_library(server_core STATIC ${TEAMSERVER_CORE_SOURCES}) +target_include_directories(server_core + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/teamServer + ${CMAKE_SOURCE_DIR}/core + ${CMAKE_SOURCE_DIR}/core/modules/ModuleCmd +) +target_link_libraries(server_core + PUBLIC + server_listener_transport + ${TEAMSERVER_CORE_LINK_LIBS} +) + +add_executable(TeamServer teamServer/main.cpp) +target_link_libraries(TeamServer PRIVATE server_core) + +add_custom_command(TARGET TeamServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $ "${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/$") +add_custom_command(TARGET TeamServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + ${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 "${C2_TEAMSERVER_RUNTIME_OUTPUT_DIR}/auth_credentials.json") + +function(teamserver_add_test target_name link_target) + add_executable(${target_name} ${ARGN}) + target_link_libraries(${target_name} PRIVATE ${link_target}) + add_custom_command(TARGET ${target_name} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $ "${C2_TEST_BIN_OUTPUT_DIR}/$") + add_test(NAME ${target_name} COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") +endfunction() if(WITH_TESTS) - add_executable(testsTestServer tests/testsTestServer.cpp ) - target_link_libraries(testsTestServer ) + teamserver_add_test(testsTestServer + server_core + tests/testsTestServer.cpp + ) + + teamserver_add_test(testsTeamServerHelpService + server_core + tests/TeamServerHelpServiceTests.cpp + ) - add_custom_command(TARGET testsTestServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy - $ "${CMAKE_SOURCE_DIR}/Tests/$") + teamserver_add_test(testsTeamServerCommandPreparationService + server_core + tests/TeamServerCommandPreparationServiceTests.cpp + ) - add_test(NAME testsTestServer COMMAND "${CMAKE_SOURCE_DIR}/Tests/$") -endif() \ No newline at end of file + teamserver_add_test(testsTeamServerListenerArtifactService + server_core + tests/TeamServerListenerArtifactServiceTests.cpp + ) + + teamserver_add_test(testsTeamServerSocksService + server_core + tests/TeamServerSocksServiceTests.cpp + ) + + teamserver_add_test(testsTeamServerTermLocalService + server_core + tests/TeamServerTermLocalServiceTests.cpp + ) + + teamserver_add_test(testsTeamServerListenerSessionService + server_core + tests/TeamServerListenerSessionServiceTests.cpp + ) + + teamserver_add_test(testsTeamServerHttpListenerTransport + server_listener_transport + tests/TeamServerHttpListenerTransportTests.cpp + ) +endif() diff --git a/teamServer/teamServer/TeamServer.cpp b/teamServer/teamServer/TeamServer.cpp index 39d915b..2d97028 100644 --- a/teamServer/teamServer/TeamServer.cpp +++ b/teamServer/teamServer/TeamServer.cpp @@ -1,436 +1,95 @@ #include "TeamServer.hpp" -#include +#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 #include -#include -#include using namespace std; using namespace std::placeholders; -namespace fs = std::filesystem; 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) { 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(); -} - -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; - } - } -} +std::string getIPAddress(const std::string& interface); 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) { - // 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); - } - - 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; - } - - 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()); - } - } - 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); - - // 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_)); - - m_moduleCmd.back()->setDirectories(m_teamServerModulesDirectoryPath, - m_linuxModulesDirectoryPath, - m_windowsModulesDirectoryPath, - m_linuxBeaconsDirectoryPath, - m_windowsBeaconsDirectoryPath, - m_toolsDirectoryPath, - m_scriptsDirectoryPath); - - 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"); - } - - 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_logger = createTeamServerLogger(config); + + TeamServerRuntimeConfig runtimeConfig = TeamServerRuntimeConfig::fromJson(config); + runtimeConfig.validateDirectories(m_logger); + runtimeConfig.configureCommonCommands(m_commonCommands); + + 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, + 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); }); + 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, + [this]() + { return m_moduleLoader->loadModules(); }); + + m_moduleCmd = m_moduleLoader->loadModules(); m_handleCmdResponseThreadRuning = true; m_handleCmdResponseThread = std::make_unique(&TeamServer::handleCmdResponse, this); @@ -440,144 +99,25 @@ 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) { (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 // 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 @@ -587,163 +127,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) @@ -751,172 +135,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) @@ -924,221 +160,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; -} - -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; + return m_listenerSessionService->stopSession(*sessionToStop, response); } grpc::Status TeamServer::SendCmdToSession(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::Response* response) @@ -1146,349 +168,34 @@ 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"); - - return 0; -} - -grpc::Status TeamServer::GetResponseFromSession(grpc::ServerContext* context, const teamserverapi::Session* session, grpc::ServerWriter* writer) -{ - 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; -} - -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; + while (m_handleCmdResponseThreadRuning) + m_listenerSessionService->handleCmdResponse(); + return 0; +} - m_logger->trace("GetHelp end"); +grpc::Status TeamServer::GetResponseFromSession(grpc::ServerContext* context, const teamserverapi::Session* session, grpc::ServerWriter* writer) +{ + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; + return m_listenerSessionService->streamResponsesForSession( + session->beaconhash(), + context->client_metadata(), + [&](const teamserverapi::CommandResponse& commandResponse) + { return writer->Write(commandResponse); }); +} - return grpc::Status::OK; +grpc::Status TeamServer::GetHelp(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::CommandResponse* commandResponse) +{ + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; + return m_helpService->getHelp(*command, commandResponse); } // Split input based on spaces and single quotes @@ -1525,7 +232,7 @@ void static inline splitInputCmd(const std::string& input, std::vectordebug("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; - } - } - else if (instruction == PutIntoUploadDirInstruction) - { - 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) + if (m_listenerArtifactService->canHandle(instruction)) { - 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; + return m_listenerArtifactService->handleCommand(instruction, splitedCmd, *command, response); } - // TODO - else if (instruction == ReloadModulesInstruction) + else if (m_termLocalService->canHandle(instruction)) { - 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); - moduleCmdPtr->setDirectories( - m_teamServerModulesDirectoryPath, - m_linuxModulesDirectoryPath, - m_windowsModulesDirectoryPath, - m_linuxBeaconsDirectoryPath, - m_windowsBeaconsDirectoryPath, - m_toolsDirectoryPath, - m_scriptsDirectoryPath); - - 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_) { 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 @@ -2286,242 +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; -} - -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(); + return m_commandPreparationService->prepareMessage(input, c2Message, isWindows); } diff --git a/teamServer/teamServer/TeamServer.hpp b/teamServer/teamServer/TeamServer.hpp index cdfed32..c284da7 100644 --- a/teamServer/teamServer/TeamServer.hpp +++ b/teamServer/teamServer/TeamServer.hpp @@ -28,6 +28,15 @@ #include "nlohmann/json.hpp" +class TeamServerAuthManager; +class TeamServerHelpService; +class TeamServerListenerSessionService; +class TeamServerListenerArtifactService; +class TeamServerModuleLoader; +class TeamServerSocksService; +class TeamServerCommandPreparationService; +class TeamServerTermLocalService; + class TeamServer final : public teamserverapi::TeamServerApi::Service { @@ -57,9 +66,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; @@ -71,24 +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; - - // 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; @@ -96,10 +84,12 @@ 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; + 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/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/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/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": { 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/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/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/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/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/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/teamServer/TeamServerTermLocalService.cpp b/teamServer/teamServer/TeamServerTermLocalService.cpp new file mode 100644 index 0000000..d5a382a --- /dev/null +++ b/teamServer/teamServer/TeamServerTermLocalService.cpp @@ -0,0 +1,232 @@ +#include "TeamServerTermLocalService.hpp" + +#include + +#include "TeamServerModuleLoader.hpp" +#include "listener/ListenerHttp.hpp" +using json = nlohmann::json; + +namespace +{ +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 +{ + TeamServerModuleLoader loader(m_logger, m_runtimeConfig); + return loader.loadModules(); +} + +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/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/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/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; +} diff --git a/teamServer/tests/TeamServerHttpListenerTransportTests.cpp b/teamServer/tests/TeamServerHttpListenerTransportTests.cpp new file mode 100644 index 0000000..a09b24b --- /dev/null +++ b/teamServer/tests/TeamServerHttpListenerTransportTests.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include + +#include + +#include "ListenerHttp.hpp" + +#ifndef _WIN32 +#include +#include +#include +#include +#endif + +namespace +{ +#ifndef _WIN32 +int findFreePort() +{ + const int sock = ::socket(AF_INET, SOCK_STREAM, 0); + assert(sock >= 0); + + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + const int bindResult = ::bind(sock, reinterpret_cast(&addr), sizeof(addr)); + assert(bindResult == 0); + + socklen_t len = sizeof(addr); + const int nameResult = ::getsockname(sock, reinterpret_cast(&addr), &len); + assert(nameResult == 0); + + const int port = ntohs(addr.sin_port); + ::close(sock); + return port; +} +#else +int findFreePort() +{ + return 18080; +} +#endif + +void waitForListenerReady(int port) +{ + httplib::Client cli("127.0.0.1", port); + cli.set_connection_timeout(0, 100000); + cli.set_read_timeout(0, 100000); + cli.set_write_timeout(0, 100000); + + for (int attempt = 0; attempt < 50; ++attempt) + { + if (auto res = cli.Post("/checkin", "", "text/plain")) + { + if (res->status == 200) + return; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + assert(false && "listener did not become ready"); +} + +void testHttpAndWebSocketTransport() +{ + const int port = findFreePort(); + nlohmann::json config = { + {"LogLevel", "off"}, + {"ListenerHttpConfig", + { + {"uri", {"/checkin"}}, + {"wsUri", {"/ws"}}, + {"uriFileDownload", "/downloads/"}, + {"downloadFolder", "."}, + {"wsMaxMessageSize", 4096}, + {"server", + { + {"headers", + { + {"Content-Type", "application/json"}, + {"Server", "Server"}, + }}, + }}, + }}, + }; + + ListenerHttp listener("127.0.0.1", port, config, false); + assert(listener.init() == 1); + waitForListenerReady(port); + + httplib::Client cli("127.0.0.1", port); + auto ok = cli.Post("/checkin", "", "text/plain"); + assert(ok); + assert(ok->status == 200); + + auto denied = cli.Post("/forbidden", "", "text/plain"); + assert(denied); + assert(denied->status == 401); + + httplib::ws::WebSocketClient ws("ws://127.0.0.1:" + std::to_string(port) + "/ws"); + assert(ws.is_valid()); + assert(ws.connect()); + assert(ws.send("")); + + std::string reply; + const auto result = ws.read(reply); + assert(result == httplib::ws::Text); + assert(reply.empty()); + ws.close(); +} +} // namespace + +int main() +{ + testHttpAndWebSocketTransport(); + 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; +} 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; +} 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; +} 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; +} 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 +} diff --git a/thirdParty/CMakeLists.txt b/thirdParty/CMakeLists.txt index a2e4fec..c7b7d18 100644 --- a/thirdParty/CMakeLists.txt +++ b/thirdParty/CMakeLists.txt @@ -1,7 +1,15 @@ +add_library(c2_base64 INTERFACE) + +target_sources(c2_base64 INTERFACE + ${CMAKE_SOURCE_DIR}/thirdParty/base64/base64.cpp +) + +target_include_directories(c2_base64 INTERFACE + ${CMAKE_SOURCE_DIR}/thirdParty/base64 +) + # donut file(COPY ${CMAKE_SOURCE_DIR}/thirdParty/donut DESTINATION ${CMAKE_BINARY_DIR}/thirdParty) execute_process(COMMAND bash -c "cd ${CMAKE_BINARY_DIR}/thirdParty/donut && make -f Makefile") set(Donut "${CMAKE_BINARY_DIR}/thirdParty/donut/lib/libdonut.a" PARENT_SCOPE) set(aplib64 "${CMAKE_BINARY_DIR}/thirdParty/donut/lib/aplib64.a" PARENT_SCOPE) - - 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